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() }) }
@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 decorated variables allow to change the values parent @State/@ObservedObject variables
@Binding var currentPage: Int
@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
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)
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)) } }
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 } }
extension Animation { static func ripple() -> Animation { Animation.default } }
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 })
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))
Button(action: { }){ Image(systemName: "plus") .resizable() .frame(width:25, height: 25) .foregroundColor(Color(.systemIndigo)) .padding(25) } .background(Color(.blue).opacity(0.3)) .clipShape(Circle())
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())
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 } } }
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 } } } }
cmd+alt+[ - move line upcmd+alt+P - resume previewctrl+I - indent selectedcmd+B - buildalt+cmb+P - resume preview