Skip to content

prethep/LearningSwiftUI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alt text

Project 0: Landmarks

  • Edit the VStack initializer to align the views by their leading edges.
VStack(alignment: .leading) { ... }
  • A spacer expands to make its containing view use all of the space of its parent view, instead of having its size defined only by its contents.

  • Create a custom image view.

struct CircleImage: View {
    var body: some View {
        Image("turtlerock")
            .clipShape(Circle())
            .overlay(
                Circle().stroke(Color.white, lineWidth: 4))
            .shadow(radius: 10)
    }
}
  • To use UIView subclasses from within SwiftUI, you wrap the other view in a SwiftUI view that conforms to the UIViewRepresentable protocol.
import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let coordinate = CLLocationCoordinate2D(
            latitude: 34.011286, longitude: -116.166868)
        let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}
  • To layer the image view on top of the map view, give the image an offset of -130 points vertically, and padding of -130 points from the bottom of the view.
CircleImage()
    .offset(y: -130)
    .padding(.bottom, -130)
  • To extend to the top edge of the screen, add the edgesIgnoringSafeArea(.top) modifier to the map view.

Project 1: ListedPhotosViewer

  • Explore the Combine Framework:
  class DataSource: BindableObject {
    // PassthroughSubject< send nothing and never throw errors >
    var didChange = PassthroughSubject<Void, Never>()
    var photos = [String]()
    
    init() {
      ...
      didChange.send(())
    }
  }
  • Learn about the BindableObject protocol
  • Notify about changes using didChange.send(())
  • Understand the difference between @State and @ObjectBinding
  • Use someArray.identified(by: .self) instead of conforming to the Identifiable protocol

Project 2: FlagGuessing

  • Declare all @State variables as private when possible (recommended by Apple)
  • Let alerts appear based on a boolean @State variable (declarative way):
.presentation($showingAlert) { 
  // Present alert
  
  // SwiftUI will automatically toggle 'showingAlert' variable.
}
  • Creation of an Alert and attaching a custom action to the dismiss button:
Alert(title: Text(alertTitle), message: Text(score), dismissButton: .default(Text("Continue")) {
  self.nextQuestion()
}) 

Project 3: iBeaconDetector

  • How to let a BindableObject conform to a delegate
class BeaconDetector: NSObject, BindableObject, CLLocationManagerDelegate {
    var didChange = PassthroughSubject<Void, Never>()
    ...
    override init() { // overrides NSObject init
        super.init()
        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.requestWhenInUseAuthorization()
    }
    ...
    func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], 
                            satisfying beaconConstraint: CLBeaconIdentityConstraint) {
       ...
    }
    ...
}
  • Modifier Sequence matters (everytime a modifier is added, a new view is created.)
  • To ignore all safe area:
  .edgesIgnoringSafeArea(.all)
  • Creating a custom modifier:
struct BigText: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(Font.system(size: 72, design: .rounded))
            .frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
    }
}
Text("RIGHT HERE")
        .modifier(BigText())
  • Complex logic inside a View may require the 'return' keyword e.g.:
struct ContentView : View {
    @ObjectBinding var detector = BeaconDetector()
    var body: some View {
        if detector.lastDistance == .immediate {
            return Text("RIGHT HERE")
                .modifier(BigText())
                .background(Color.red)
                .edgesIgnoringSafeArea(.all)
        } else if detector.lastDistance == .near {
            return Text("NEAR")
                .modifier(BigText())
                .background(Color.orange)
                .edgesIgnoringSafeArea(.all)
    ...
}

Project 4: BetterRest

  • Setting up a DatePicker:
DatePicker($wakeUp, displayedComponents: .hourAndMinute)
  • Setting up a Stepper:
Stepper(value: $sleepAmount, in: 4...12, step: 0.25) {
    Text("\(sleepAmount, specifier: "%g") hours")
}
  • Rounding up "8.0000.." -> 8 and "8.2500000" -> 8.25:
Text("\(16.0/2.0, specifier: "%g"))
  • Create a trailing navigation bar button:
.navigationBarItems(trailing:
    Button(action: calculateBedtime) {
        Text("Calculate")
    }
)
  • Presenting an alert:
.presentation($showingAlert) {
    Alert(title: Text(alertTitle), message: Text(alertMessage), dismissButton: .default(Text("OK")))
  • Change only hours and minutes from current DateTime():
    static var defaultWakeTime: Date {
        var components = DateComponents()
        components.hour = 8
        components.minute = 0
        return Calendar.current.date(from: components) ?? Date()
    }
  • Use CoreML Model to predict:
func calculateBedtime() {
    let model = SleepCalculator()

    do {
        let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
        let hour = (components.hour ?? 0) * 60 * 60
        let minute = (components.minute ?? 0) * 60

        let prediction = try model.prediction(coffee: Double(coffeeAmount), estimatedSleep: Double(sleepAmount), wake: Double(hour + minute))

        let formatter = DateFormatter()
        formatter.timeStyle = .short

        let sleepTime = wakeUp - prediction.actualSleep
        alertMessage = formatter.string(from: sleepTime)
        alertTitle = "Your ideal bedtime is..."
    } catch {
        alertTitle = "Error"
        alertMessage = "Sorry, there was a problem calculating your bedtime."
    }

    showingAlert = true

}

Project 5: WordScramble

  • Call a function onAppear
VStack {
  ...
}
.onAppear {
    self.startGame()
}
  • Add a 'on commit' closure (on return key pressed) to a textfield and hide the keyboard.
  TextField($newWord) {
      // On commit closure
      self.addNewWord()
      UIApplication.shared.keyWindow?.endEditing(true)
  }
  • Add textfield styles
  TextField($newWord) {
      ...
  }
  .textFieldStyle(.roundedBorder)
  .padding()

Project 6: CupcakeCorner

  • Make a Bindable Object 'Codable' by ignoring the PassthroughSubject
class Order: BindableObject, Codable {
    enum CodingKeys: String, CodingKey {
        case type, quantity, extraFrosting, addSprinkles, name, streetAddress, city, zip
    }
    ...
}
  • Call 'didChange.send(())' using a 'didSet'
    ..
    var type = 0 { didSet { update() } }
    var quantity = 3 { didSet{ update() } }
    
    func update() {
        didChange.send(())
    }

About

Learning about Apple's recently (WWDC 19) announced declarative UI framework, SwiftUI.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages