Mobile App Development 2022W Lecture 5
Video
Video from the lecture given on January 26, 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 5 --------- recall: @State variables update their view (and child views) when they are changed automatically @Binding variables allow access to the @State variables in a parent view (so, kind of like a pointer/reference) GeometryReader gives us the geometry of the enclosing view - what are the dimensions Note that child views are given a box for where they should draw their stuff by their parent view. But the child views doesn't have to follow these guidelines, it can draw itself wherever it wants When we include one view inside another, the parent view can pass data to a child view - By default, data is passed by value, like a function call - we can pass by reference (so changes in the parent are reflected in the child), that's what we do with @State/@Binding/$ General programming language terminology - call by value: you call a function with parameters, changes to those parameter values won't be seen by the caller - call by reference: changes to parameter values in the function *will* be seen by the caller, because both are actually accessing the same underlying variable In C, all functions are call by value, but you can pass in pointers to get call by reference semantics In python, dictionaries/objects are always call by reference (I believe), but otherwise it is call by value What we're really talking about here is, how can a function call affect the caller. - do we just care about the return value? - or will there be side effects on the data passed in? In other words, will the function mutate our data, or is data immutable? Older programming languages were all based on mutable state - it more closely matches how the underlying hardware works - can be very efficient - but, it can lead to errors/hard to debug code, because you have to consider *who* had access to the data, as any of them could have messed it up Languages like Swift, and frameworks like SwiftUI, are focused on immutable data - less error prone, a value never changes - but, can be inefficient - magic of Swift and SwiftUI, though, is it is actually very efficient (they achieved this through a lot of low-level complexity that they try to hide but can be exposed) - if you want something mutable, you have to take extra steps and if you don't do it right the system will fight you Remember: "let" makes an immutable binding between a name and a value "var" creates a variable that can take on different values at differen times So let's inside of a View are fine, because they are immutable But assignments to variables are *not* allowed, because they are mutable but Views are immutable Another way of thinking of SwiftUI views is that they are written in a domain specific language (the SwiftUI view language) - so the rules for it are different from the rest of Swift (To be specific, Views are created using ViewBuilder, which is based on ResultBuilder, a way to create domain-specific languages in Swift) What is a domain-specific language? - it is just a language designed to solve specific kinds of problems (problems in a given "domain") - idea is the syntax and semantics of the language are designed to match the problem, and so make it easier to solve the problem using the least amount of code Say I wanted to create solutions to automatically putting grocery items on shelves - I could just make an API with specific functions to do the operations - or, I could create a little language for describing the problem of putting grocery items on shelves (or anything on shelves) (Really, custom APIs are DSLs where we can't really mess with the syntax) The rules of SwiftUI don't match those of Swift, even though SwiftUI is implemented in Swift - because Swift lets you do weird things So, what are we really doing when we make GUI applications? We have a few key things: frame buffer: memory representation of what's on screen (change values in the frame buffer, what's on the screen changes) input devices: user does an action, program gets the input (mouse, keyboard, touchscreen) our code: handles input, does output by changing the contents of the frame buffer Classic way we organize our code is as an event driven system - user input generates an event - our code processes the event and updates the screen (and internal data) - our code then waits for more user input If we want to do other things while waiting for input we do it in parallel or in small chunks so we can always be ready to handle user input - if we aren't ready to handle user input in a timely fashion we have input lag/unresponsive interfaces and users get annoyed But how does this map onto SwiftUI? - our code doesn't directly update the screen - instead, we describe what the screen should look like *at all times* through views - the screen should be a function of some data - data can either be constant or variable - if variable, it should be put into a state variable The views are automatically redrawn (i.e., the framebuffer is updated) whenever a state variable is changed - it is evaluating *all the views* - this could be very inefficient, except SwiftUI is very clever in how it does updates When we move to Android development (and later still see iOS Storyboard development), we'll see a genuine event loop - we'll have event handlers that will update the screen And you'll see the code gets much longer and much more complex When we say views are immutable, we just mean they are immutable while they are being updated - that's all. - and that's why we can't change variables in views, it would make it impossible for SwiftUI to calculate deterministically how the screen should change while evaluating the views (the views can't change while it is being drawn) - we *can* change state inside of closures that are called when events happen - because that is outside of the View updates So we can't change things dynamically inside of views, state updates must happen as a result of specific events (and thus in the callbacks associated with those events)