COMP 1601 2021W Tutorial 4 Solutions 1. Build and run the program. Select different images from the menu. Drag, rotate, scale, and tap on the images. What happens? (Note that you can do pinch and rotate operations in the simulator by holding down the option key and dragging the circles that appear.) The menu lets you select between two pictures, one of kittens and one of a sad dog. Dragging an image moves its position on the screen. Rotate and scale (two fingers to rotate and zoom) rotates and scales the image. Selecting a picture from the menu resets it to its original size and position in the middle of the screen. Tapping on the image does the same thing. 2. Add an image to the app's assets. Add that image to the menu so it can be selected like the others. What did you have to do? To add an image to the menu, first add it to Assets.xcassets by making a new image set (selecting the + at the bottom) and then dragging and dropping an image onto the 1x square. Give it a name like "newImage". Then, create an additional button in the menu by copying how the other buttons are made and inserting the new button between lines 24 and 25 as follows: Button("New Image", action: { theImage = "newImage" resetState() }) With this code, newImage will be selected when the user selects the "New Image" option. 3. If you remove the VStack on line 16, does that change the program's behaavior? Why or why not? Removing the VStack doesn't seem to change the program's behavior. This seems to be because a ContentView view is implicitly a VStack by default. 4. Why are so many state variables declared in ContentView but used in ActiveImage? Why not keep them local to ActiveImage? Explain for each parameter. These variables are declared in ContentView because the menu is defined here, and when a menu item is selected all of them need to be changed. Details are below. theImage: This string specifies the image to be displayed. It needs to be here because the menu that changes it is in ContentView while it is used in ActiveImage moved: This boolean indicates whether the image has been moved or not. It is changed in ContentView in resetState() (so the image is marked as not having moved when a new image is selected) and is used in ActiveImage to decide whether to position the image in the middle of the screen or where the user has dragged it. finalAmount: This CGFloat stores the magnification factor. It is changed in ActiveImage on drag and used to scale the image. In ContentView it is changed in resetState() just like moved. angle: This Angle object stores the rotation factor for the image. It is reset resetState() like finalAmount and moved but is otherwise used and updated in ActiveImage. 5. Why is the moved variable needed? Why not always just set the position to the position variable? The moved parameter is needed because we need to determine the initial position of the image based on the geometry of the screen (specifically, the region of the screen that SwiftUI gives us to place the image), but GeometryReader only gives us these values at runtime and so we cannot use them for the initial value of position. The solution here is to use the GeometryReader values if the user hasn't dragged the image, and if they have, use the drag position/destination for position of the circle. 6. What's the purpose of resetState()? resetState() resets the state of the image after a menu item is selected. Otherwise, the old position, zoom, and orientation will be used for the new image selected. 7. Change the program so that when you single tap it switches between rotating or magnifying modes (rather than doing both at once). Make double tap reset the image. There are multiple ways to do this. The key insight is that SwiftUI makes it difficult to dynamically add and remove event handlers like gesture handlers. So, instead you change the code of the handlers to change their behavior when a state variable is changed. Below is one way to do this. Define a state variable in ContentView: @State private var magnifyingMode = false Then, define a corresponding binding in ActiveImage @Binding var magnifyingMode: Bool In ContentView, reset its value when a menu item is selected by adding the following to resetState: magnifyingMode = false And then change magnifying and rotating properties of ActiveImage as follows: var rotating: some Gesture { RotationGesture() .onChanged { angle in if !magnifyingMode { self.angle = angle } } } var magnifying: some Gesture { MagnificationGesture().onChanged { amount in if magnifyingMode { self.currentAmount = amount - 1 } } .onEnded { amount in if magnifyingMode { self.finalAmount += self.currentAmount self.currentAmount = 0 } } } And finally toggle magnifyingMode on a single click and reset the view on double click by replacing (on line 52): .onTapGesture(count: 1, perform: tapReset) with .onTapGesture(count: 2, perform: tapReset) .onTapGesture(count: 1, perform: { magnifyingMode.toggle() }) Note that .toggle() just makes a boolean value false if it is true and true if it is false. (You could just use an if/else statement.)