Core Wiki

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
swiftui [2020/04/14 11:10]
august [References]
swiftui [2020/04/14 11:19] (current)
august [Customisation]
Line 1: Line 1:
 +====== Swift UI ======
 +===== References =====
 +  * [[https://​stackoverflow.com/​questions/​56491881/​move-textfield-up-when-thekeyboard-has-appeared-by-using-swiftui-ios|Move screen on Keyboard entry]]
 +  * [[https://​www.hackingwithswift.com/​quick-start/​swiftui/​how-to-provide-relative-sizes-using-geometryreader|Accessing view geometry]]
 +  * [[https://​www.hackingwithswift.com/​quick-start/​swiftui/​whats-the-difference-between-observedobject-state-and-environmentobject|State,​ ObservedObject,​ EnvironmentObject]]
 +  * [[https://​developer.apple.com/​design/​human-interface-guidelines/​ios/​overview/​themes/​|iOS Design guidelines]]
 +  * [[https://​www.hackingwithswift.com/​quick-start/​swiftui/​swiftui-tips-and-tricks|Tips and Tricks]]
 +  * [[https://​medium.com/​@axelhodler/​creating-a-search-bar-for-swiftui-e216fe8c8c7f|Search Bar]]
 +  * [[https://​stackoverflow.com/​questions/​56822195/​how-do-i-use-userdefaults-with-swiftui|UserDefaults]]
 +
 +==== Customisation ===
 +  * [[https://​www.youtube.com/​watch?​v=mCtohqZ6cYg|TabView]]
 +  * [[https://​github.com/​smartvipere75/​bottombar-swiftui|BottomBar animated]]
 +===== API =====
 +To enable networking add to ''​plist.info'':​ ''​App Transport Security Settings''​ and inside of it ''​Allow Arbitrary Loads''​
 +===== Navigation View =====
 +Dynamic list generation
 +<code bash>
 +NavigationView {
 +    List {
 +        ForEach(categories.keys.sorted(),​ id: \.self) { key in
 +            Text(key)
 +        }
 +    }
 +}
 +</​code>​
 +
 +''​NavigationLink''​ to other views (''​NavigationLink''​ renders with environment colours, use ''​.foregroundColor(.primary)''​ on Text and ''​.renderingMode(.original)''​ on Image)
 +<​code>​
 +NavigationLink(destination:​ LandmarkDetail(landmark:​ landmark)) {
 + ​Object{}
 +}
 +</​code>​
 +
 +Navigation view title is applied to the child
 +<code swift>
 +NavigationView {
 +   ​VStack {
 +      Text("​x"​)
 +   }
 +   ​.navigationBarTitle(Text("​Title"​))
 +   ​.navigationBarTitle(Text(hike.name),​ displayMode:​ .inline)
 +   ​.navigationBarItems(leading:​ EditButton(),​ trailing: Button("​Add New Order"​) {
 +       ​self.isPresented.toggle()
 +    })
 +}
 +</​code>​
 +===== 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.
 +<code swift>
 +@State var showingProfile = false
 +self.showingProfile.toggle()
 +.sheet(isPresented:​ $showingProfile) {}
 +</​code>​
 +
 +=== Binding ===
 +@Binding decorated variables allow to change the values parent @State/​@ObservedObject variables
 +<code swift>
 +@Binding var currentPage:​ Int
 +</​code>​
 +
 +=== EnvironmentObject ===
 +''​@EnvironmentObject'' ​ An environment object invalidates the current view whenever the observable object changes.
 +<code swift>
 +@EnvironmentObject var userData: UserData
 +</​code>​
 +The parent object has to pass ''​UserData''​ as
 +<code swift>
 +childView.environmentObject(UserData())
 +</​code>​
 +
 +For Preview mode it's necessary to provide environmentObject to ''​PreviewProvider''​
 +
 +===== Library objects =====
 +View
 +<code swift>
 +SomeView
 +.listRowInsets(EdgeInsets()) // stretches to screen edges
 +.padding([.leading,​ .bottom])
 +</​code>​
 +
 +Text
 +<code swift>
 +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))
 +</​code>​
 +
 +
 +HStack
 +<code swift>
 +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)
 +</​code>​
 +
 +VStack
 +<code swift>
 +VStack(alignment:​ .leading) {}
 +.frame(minWidth:​ 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
 +.edgesIgnoringSafeArea(.all)
 +</​code>​
 +
 +ZStack
 +<code swift>
 +ZStack {
 +   ​Color(.label).opacity(0.05).edgesIgnoringSafeArea(.all)
 +}
 +</​code>​
 +
 +Image
 +<code swift>
 +image
 +.resizable()
 +.aspectRatio(contentMode:​ .fit)
 +.frame(width:​ 155, height: 155)
 +.cornerRadius(5)
 +.accessibility(label:​ Text("​User Profile"​))
 +.renderingMode(.original)
 +.clipShape(Circle())
 +.scaledToFit()
 +</​code>​
 +
 +Picker
 +<code swift>
 +Picker("",​ selection: self.$addCofeeOrderVM.size) {
 +    Text("​Small"​).tag("​Small"​)
 +    Text("​Medium"​).tag("​Medium"​)
 +    Text("​Large"​).tag("​Large"​)
 +}.pickerStyle(SegmentedPickerStyle())
 +</​code>​
 +
 +ForEach
 +<code swift>
 +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)
 +</​code>​
 +===== Preview =====
 +Add multiple phone models to preview
 +<code swift>
 +ForEach(["​iPhone SE", "​iPhone XS Max"], id: \.self) { deviceName in
 +    LandmarkList()
 +        .previewDevice(PreviewDevice(rawValue:​ deviceName))
 +        .previewDisplayName(deviceName)
 +        .colorScheme(.dark)
 +}
 +</​code>​
 +
 +If the phone screen is not needed, more compact display method is
 +<code swift>
 +viewObject().previewLayout(.sizeThatFits)
 +</​code>​
 +
 +@Binding variables can be simulated in the preview using ''​.constant''​ function.
 +<code swift>
 +//some view
 +@Binding var score: Int
 +</​code>​
 +<code swift>
 +struct FancyScoreView_Previews:​ PreviewProvider {
 +    static var previews: some View {
 +        FancyScoreView(score:​ .constant(0))
 +    }
 +}
 +</​code>​
 +===== Transitions =====
 +It's recommenced to define custom transitions by extending ''​AnyTransition''​ (''​moveAndFade''​ can be accessed using dot notation)
 +<code swift>
 +extension AnyTransition {
 +    static var moveAndFade:​ AnyTransition {
 +        AnyTransition.slide
 +    }
 +}</​code>​
 +
 +===== Animations =====
 +<code swift>
 +extension Animation {
 +    static func ripple() -> Animation {
 +        Animation.default
 +    }
 +}
 +</​code>​
 +
 +===== Gestures =====
 +Tap gesture
 +<code swift>
 +Card()
 +.gesture(TapGesture(count:​ 1)
 +    .onEnded{
 +        print("​Tapped"​)
 +})
 +</​code>​
 +
 +Drag gesture
 +<code swift>
 +@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
 +}
 +</​code>​
 +
 +Scale gesture
 +<code swift>
 +@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
 +})
 +</​code>​
 +
 +Rotation gesture
 +<code swift>
 +@State private var cardRotateState:​ Double = 0
 +
 +Card()
 +.gesture(RotationGesture()
 +    .onChanged { value in
 +        self.cardRotateState = value.degrees
 +    }
 +    .onEnded{ value in
 +        self.cardRotateState = 0
 +})
 +</​code>​
 +===== Other =====
 +Set frame for image before scaling
 +<code swift>
 +image
 +.frame(width:​300,​ height:300)
 +.scaleEffect(1.0/​3.0)
 +.frame(width:​ 100, height: 100)
 +</​code>​
 +
 +
 +Colors
 +<code bash>
 +TextField("​Username",​ text: $username)
 +.background(Color(UIColor.systemGray5))
 +</​code>​
 +
 +  * [[https://​developer.apple.com/​design/​human-interface-guidelines/​ios/​visual-design/​color/​|ColorCodes]]
 +  * [[https://​www.fivestars.blog/​code/​ios-dark-mode-how-to.html|Semantic Colors]]
 +
 +
 +===== Snippets =====
 +===Plus button circled with dynamic colors===
 +<code swift>
 +Button(action:​ {
 +    ​
 +}){
 +    Image(systemName:​ "​plus"​)
 +        .resizable()
 +        .frame(width:​25,​ height: 25)
 +        .foregroundColor(Color(.systemIndigo))
 +        .padding(25)
 +}
 +.background(Color(.blue).opacity(0.3))
 +.clipShape(Circle())
 +</​code>​
 +
 +===Custom border rounding===
 +<code swift>
 +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)
 +    }
 +}
 +</​code>​
 +Can be applied to view or image
 +<code swift>
 +view.clipShape(shape())
 +</​code>​
 +
 +===Timer publishing values===
 +<code swift>
 +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
 +        }
 +    }
 +}
 +</​code>​
 +
 +=== ObservedObject,​ ObservableObject,​ Published ===
 +<code swift>
 +import Foundation
 +import SwiftUI
 +import Combine
 +
 +class UserSettings:​ ObservableObject {
 +    ​
 +    @Published var score: Int = 0
 +    ​
 +}
 +</​code>​
 +<code swift>
 +struct ContentView:​ View {
 +    ​
 +    @ObservedObject var userSettings = UserSettings()
 +    ​
 +    var body: some View {
 +        VStack {
 +            Text("​\(userSettings.score)"​)
 +                .font(.largeTitle)
 +            ​
 +            Button("​Increment Score"​) {
 +                self.userSettings.score += 1
 +            }
 +        }
 +    }
 +}
 +</​code>​
 +===== 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
 +