COMP 1601 2022W Assignment 2 Solutions 1. [1] Explain how to add this image from Wikipedia to the image list under the name "Boxer". A: Insert the following between lines 18 and 19 (to the images dictionary): "Boxer": https://upload.wikimedia.org/wikipedia/commons/6/6f/Male_fawn_Boxer_undocked.jpg 2. [2] What name is added to the menu if we manually enter a URL and select "Remember Image"? Why? Explain with an example. A: The last component of the URL's path is added as the menu item. Specifically, the path is split on '/' characters and the last split is used as the menu item. With the Boxer URL, the menu entry becomes "Male_fawn_Boxer_undocked.jpg" because that's what is after the last / in the URL. Note that this functionality is implemented in lines 50-53, in the action closure associated with the Remember Image button. 3. [2] There is an image URL on line 63, in getImage(). When is this image displayed during normal program operation? Why? A: This image, year.jpg, is never displayed because imageName is always a valid entry in the images dictionary. It is initialized to Kittens and then is only changed using menu actions, each of which always refer to a valid entry in images. (You can see the image if you set the initial value of imageName on line 23 to be something not in the dictionary, such as "Nothing".) 4. [2] If you delete line 28 and instead replace line 20 with "var imageNames = [String] (images.keys)", the program will compile and run. Will it function any differently? Why? A: It will function as before except remembering an image won't work - no new entries will appear in the menu. This happens because SwiftUI won't update the menu when the dictionary is changed, as it isn't a @State variable anymore. However the menu may update when another state variable is changed (say, by typing something in), as that will trigger the views to be redrawn. (Since SwiftUI optimizes view updates, you can't be sure when exactly the menu will be refreshed.) 5. [2] Why does the program have both currentAmount and finalAmount for implementing the magnification gesture? Specifically, how does the program behave if we use finalAmount in the onChanged handler and do nothing in the onEnded handler of magnifying? A: If we just use finalAmount in the onChanged handler and don't do anything in the onEnded handler, basic zoom still works. However, now every zoom is proportional to the current gesture and the effects of past zoom gestures are forgotten. In other words, you can't zoom and then zoom again; in effect we're directly specifying the zoom factor with the gesture. Thus, we use currentAmount and finalAmount so the user can do a chain of zoom gestures to further magnify the image. 6. [2] What do each line do in lines 50-53? These implement the action function of the "Remember Image" menu option. A: Here are the lines and explanations: let urlParts = theImage.split(separator: "/") This creates an array out of the string theImage by splitting theImage at '/' characters and names it urlParts. let newName = String(urlParts[urlParts.count - 1]) This extracts the last element of urlParts, converts it from a Substring to a String, and names it newName. imageNames.append(newName) This appends newName to the array imageNames, thus adding newName to the list of menu items. images[newName] = theImage This adds an entry to the images dictionary so that when newName is looked up we get theImage, the URL of the current image being displayed. Thus now when newName is selected from the menu it will be associated with the right image URL. 7. [2] The dragging gesture handler (lines 127-137) has both an onChanged and onEnded handler that have exactly the same code in them. What does their code do? How does the program function with just one or the other? A: This code updates the position of the image based on where it has been dragged to. The onChanged handler does so as the images is being dragged, while the onEnded updates the position once the drag has finished. If you just have the onEnded handler, the image doesn't move until you stop dragging; when you do, it moves immediately to the new position. If you just have the onChanged handler, the program works as before. The onEnded handler is actually not needed. 8. [1] How does the program's behavior change if we replace line 90 with ".onTapGesture(count: 3, perform: tapReset)"? A: With this change the program no longer recognizes the long press gesture. Single tapping again causes the image to rotate. Double tapping also causes the image to rotate, but after a delay, and triple tap resets the image. Double tap no longer zooms the image, even though a handler is defined for it. Thus setting a triple tap gesture interferes with the double tap gesture. This appears to be a bug in SwiftUI. Note that triple tap actions are very uncommon (and rather unintuitive) so this use case probably hasn't been tested. 9. [2] When typing in a new URL, when is AsyncImage updated? Does it try loading the URL as you type, or does it wait until the URL is fully entered? Explain briefly. A: AsyncImage is updated as you type. When the URL doesn't exist, it just shows the progress spinner, but once we type in a URL that corresponds to a valid image it is loaded and displayed. This happens because the views are updated as we type because those characters are being stored in a state variable, theImage, and changes to state variables cause referencing views to be immediately refreshed. 10. [2] How could you change the program so that the double click zoom command only zooms in three times and on the fourth it goes back to a magnification of 1 (no zoom)? Your solution shouldn't add any new variables. A: Replace the zoom() function (lines 102-104) with the following: func zoom() { if (self.finalAmount >= 8) { self.finalAmount = 1 } else { self.finalAmount = self.finalAmount * 2 } } First click will make finalAmount 2, next will make it 4 and the third will make it 8. So if we're at 8 or more, we go back to 1. 11. [2] How does the program's behavior change if you move lines 86 and 89 to just before 83? Why? With the original version, rotations and scaling doesn't work properly if the image has been moved: rotations happen around the middle of the screen not the center of the image, and magnifications move the center of the image (so you're zooming in elsewhere). By moving the rotationEffect and scaleEffect handlers to before setting the position, the transformations are applied before the image is moved and so they work as expected.