- 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.
- 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
- 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()
})
- 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)
...
}
- 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
}
- 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()
- 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(())
}