Core Wiki

Swift UI

References

Customisation

API

To enable networking add to plist.info: App Transport Security Settings and inside of it Allow Arbitrary Loads

Dynamic list generation

NavigationView {
    List {
        ForEach(categories.keys.sorted(), id: \.self) { key in
            Text(key)
        }
    }
}

NavigationLink to other views (NavigationLink renders with environment colours, use .foregroundColor(.primary) on Text and .renderingMode(.original) on Image)

NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
 Object{}
}

Navigation view title is applied to the child

NavigationView {
   VStack {
      Text("x")
   }
   .navigationBarTitle(Text("Title"))
   .navigationBarTitle(Text(hike.name), displayMode: .inline)
   .navigationBarItems(leading: EditButton(), trailing: Button("Add New Order") {
       self.isPresented.toggle()
    })
}

UI changes / state handling

State

@State allows to read/write to value and bind to changes in its value. When the state value changes, the view invalidates its appearance and recomputes the body.

@State var showingProfile = false
self.showingProfile.toggle()
.sheet(isPresented: $showingProfile) {}

Binding

@Binding decorated variables allow to change the values parent @State/@ObservedObject variables

@Binding var currentPage: Int

EnvironmentObject

@EnvironmentObject An environment object invalidates the current view whenever the observable object changes.

@EnvironmentObject var userData: UserData

The parent object has to pass UserData as

childView.environmentObject(UserData())

For Preview mode it's necessary to provide environmentObject to PreviewProvider

Library objects

View

SomeView
.listRowInsets(EdgeInsets()) // stretches to screen edges
.padding([.leading, .bottom])

Text

Text(self.categoryName)
    .font(.headline)
    .padding(.leading, 15)
    .padding(.top, 5)
    .foregroundColor(.primary)
    .lineLimit(2)
    .fixedSize(horizontal: false, vertical: true)
    .fixedSize()
    .font(.system(size: 18))

HStack

HStack(alignment: .lastTextBaseline) {}
 
HStack(alignment: .top, spacing: 0) {}
   .frame(height: 185)
   .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
   .contentShape(Rectangle()) // allows to tapGesture on white space (use with Spacer)

VStack

VStack(alignment: .leading) {}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)

ZStack

ZStack {
   Color(.label).opacity(0.05).edgesIgnoringSafeArea(.all)
}

Image

image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 155, height: 155)
.cornerRadius(5)
.accessibility(label: Text("User Profile"))
.renderingMode(.original)
.clipShape(Circle())
.scaledToFit()

Picker

Picker("", selection: self.$addCofeeOrderVM.size) {
    Text("Small").tag("Small")
    Text("Medium").tag("Medium")
    Text("Large").tag("Large")
}.pickerStyle(SegmentedPickerStyle())

ForEach

private func delete(at offsets: IndexSet) {
    offsets.forEach { index in
        let orderVM = self.orderListVM.orders[index]
        self.orderListVM.deleteOrder(orderVM)
    }
}
 
ForEach(self.orderListVM.orders, id:\.id) { order in
}.onDelete(perform: delete)

Preview

Add multiple phone models to preview

ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
    LandmarkList()
        .previewDevice(PreviewDevice(rawValue: deviceName))
        .previewDisplayName(deviceName)
        .colorScheme(.dark)
}

If the phone screen is not needed, more compact display method is

viewObject().previewLayout(.sizeThatFits)

@Binding variables can be simulated in the preview using .constant function.

//some view
@Binding var score: Int
struct FancyScoreView_Previews: PreviewProvider {
    static var previews: some View {
        FancyScoreView(score: .constant(0))
    }
}

Transitions

It's recommenced to define custom transitions by extending AnyTransition (moveAndFade can be accessed using dot notation)

extension AnyTransition {
    static var moveAndFade: AnyTransition {
        AnyTransition.slide
    }
}

Animations

extension Animation {
    static func ripple() -> Animation {
        Animation.default
    }
}

Gestures

Tap gesture

Card()
.gesture(TapGesture(count: 1)
    .onEnded{
        print("Tapped")
})

Drag gesture

@State private var cardDragState = CGSize.zero
 
Card()
.animation(.spring())
.offset(y: self.cardDragState.height)
.gesture(DragGesture()
.onChanged{ value in
    self.cardDragState = value.translation
}
.onEnded { vaule in
    self.cardDragState = CGSize.zero
}

Scale gesture

@State private var scale: CGFloat = 1.0
 
Card()
.resizable()
.scaleEffect(self.scale)
.frame(width: 300, height: 300)
.gesture(MagnificationGesture()
    .onChanged { value in
        self.scale = value.magnitude
})

Rotation gesture

@State private var cardRotateState: Double = 0
 
Card()
.gesture(RotationGesture()
    .onChanged { value in
        self.cardRotateState = value.degrees
    }
    .onEnded{ value in
        self.cardRotateState = 0
})

Other

Set frame for image before scaling

image
.frame(width:300, height:300)
.scaleEffect(1.0/3.0)
.frame(width: 100, height: 100)

Colors

TextField("Username", text: $username)
.background(Color(UIColor.systemGray5))

Snippets

Plus button circled with dynamic colors

Button(action: {
 
}){
    Image(systemName: "plus")
        .resizable()
        .frame(width:25, height: 25)
        .foregroundColor(Color(.systemIndigo))
        .padding(25)
}
.background(Color(.blue).opacity(0.3))
.clipShape(Circle())

Custom border rounding

struct shape: Shape {
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 22, height: 22))
 
        return Path(path.cgPath)
    }
}

Can be applied to view or image

view.clipShape(shape())

Timer publishing values

import SwiftUI
import Combine
 
class FancyTimer: ObservableObject {
 
    @Published var value: Int = 0
 
    init() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
            self.value += 1
        }
    }
}

ObservedObject, ObservableObject, Published

import Foundation
import SwiftUI
import Combine
 
class UserSettings: ObservableObject {
 
    @Published var score: Int = 0
 
}
struct ContentView: View {
 
    @ObservedObject var userSettings = UserSettings()
 
    var body: some View {
        VStack {
            Text("\(userSettings.score)")
                .font(.largeTitle)
 
            Button("Increment Score") {
                self.userSettings.score += 1
            }
        }
    }
}

xCode keyboard shortcuts

  • cmd+alt+[ - move line up
  • cmd+alt+P - resume preview
  • ctrl+I - indent selected
  • cmd+B - build
  • alt+cmb+P - resume preview