Mobile App Development 2022W Lecture 8
Video
Video from the lecture given on February 4, 2022 is now available:
Video is also available through Brightspace (Resources->Zoom Meetings (Recordings, etc.)->Cloud Recordings tab). Note that here you'll also see chat messages.
Notes
Lecture 8
---------
In SwiftUI, you *really* don't want to do explicit typing because the types under the hood get crazy complicated
- I did some forced conversions and got a runtime error with a type that was several lines long
- use implicit typing wherever you can, everything works better
In lines 62-64 of remotePicViewer, there's a ternary operator checking the "moved" boolean variable
.position(moved ? position :
CGPoint(x: g.size.width / 2,
y: g.size.height / 2))
What this is saying is that if moved is true, return position, otherwise return
the CGPoint
When will moved be true?
- once the user has dragged the image
When will it be false?
- when the app starts up and when a new image has been selected
Question: why not just initialize position to be the fixed CGPoint value
(that is in the center of the GeometryView box)?
Why not just
- set position to the centered point when position is initialized?
- and reset position when we change to a new image?
- well, g is out of scope
What about at the top of GeometryReader?
- But we can't, we get an error "Type '()' cannot conform to 'View'"
What this error means is we're trying to do normal Swift things where we should be doing SwiftUI things
- i.e., everything inside a View has to be a View
The body of a View is not a normal function
- you can't set arbitrary variables, put in loops, etc
- everything needs to generate a View
The body of a View is written in a domain-specific language, SwiftUI
But that isn't the real reason
The real reason is, how often is the body of a view being evaluated?
- potentially VERY frequently
- every time the screen is updated (and with animation that can be very often)
To make View updates efficient (and more importantly, optimizable), the state of a View can't change while it is being calculated
- so you *can't* change @State variables in the body of a View, because
that would change the View's state, and so would change how it should be rendered
So if you see the error "Type '()' cannot conform to 'View'", you probably tried to modify a @State variable inside the body of a View.
- you have to do it in closures/functions that will be evaluated later
When you declare a function, you are really saying "here is code that should be run at a later time"
- so declaring a function inside of a view makes sure it won't be run
while the view is being updated
To setup gestures, we call the .gesture method of the view and pass it "some Gesture" (i.e., some struct that will follow the Gesture protocol)
Normally you won't define these manually, you'll instead use pre-defined ones for standard gestures
- MagnificationGesture
- DragGesture
- RotationGesture
- TapGesture
You can also combine these with SimultaneousGesture
There are also faster ways of declaring these with things like "onTapGesture"
- then we just have to give the actions rather than create a whole Gesture struct
For gestures, we declare functions that are called at various states during and after the gesture, such as
- onChanged <-- as the gestures is changing, this is called (potentially many times)
- onEnded <-- called once the gesture is ended
For things like .onTapGesture, which doesn't really have a "during" phase, we just have a "perform" function that is called when the action happens