Mobile App Dev 2021W: Assignment 2: Difference between revisions

From Soma-notes
No edit summary
 
(20 intermediate revisions by the same user not shown)
Line 1: Line 1:
'''This assignment is still being developed.'''
Please answer all of the following questions in the [https://homeostasis.scs.carleton.ca/~soma/mad-2021w/templates/assign2-template.txt supplied template], all of which are based on [https://homeostasis.scs.carleton.ca/~soma/mad-2021w/code/picviewer-2.zip picviewer-2], the code for which is included below.  There are six questions worth 20 points.
 
Do not turn modified applications.  Instead, when answering code writing questions, include code snippets and explain how they should be used to modify the application.  Be sure to use complete sentences when answering.  ''Be sure to list collaborators and outside sources that you used.''
 
Submit your answers via cuLearn by February 26, 2021 by 11:59 PM.


==Questions==
==Questions==


<ol>
<ol>
<li>[3] At the bottom of the screen it shows the current rotation of the image in degrees.  This rotation information stored in a different format as it was in Tutorial 4's 1601picviewer-1.
<li>[3] At the bottom of the screen it shows the current rotation of the image in degrees.  This rotation information stored in a different format than it was in Tutorial 4's picviewer-1.
</li>  
</li>  
<ol style="list-style-type:lower-alpha">
<ol style="list-style-type:lower-alpha">
<li>[1] What is the difference?</li>
<li>[1] What is the difference?</li>
<li>[2] Change the program to store the rotation information the same way 1601picviewer-1 did.  Be sure to add no new state variables. (Hint: Look at the documentation for TextField.)</li>
<li>[2] Try changing picviewer-2 to store the rotation information the same way picviewer-1 did.  What part is hard?  Why? (Hint: Look at the documentation for TextField.) '''Note:''' You ''are not'' expected to fully implement this; instead, show what would have to be implemented and why this isn't so easy by referring to appropriate documentation or articles.</li>
</ol>
</ol>
<li>[3] currentAngle() (lines 71-77):
<li>[3] currentAngle() (lines 71-77):
Line 14: Line 18:
<li>[1] What does currentAngle() do? (Explain what the function does in English.)</li>
<li>[1] What does currentAngle() do? (Explain what the function does in English.)</li>
<li>[1] When is it called?</li>
<li>[1] When is it called?</li>
<li>[1] Replace the call to currentAngle() with an expression using the ?? operator (the "Nil-Coalescing Operator").</li>
<li>[1] Replace the call to currentAngle() with an expression using the ?? operator (the [https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html "Nil-Coalescing Operator"]).</li>
</ol>
</ol>
</li>
</li>
<li>[2] Change rotations so they are quantized to 45 degree increments during and at the end of each rotation gesture.  It should still be possible to rotate to arbitrary angles by typing in an angle value.  Did you have to change multiple parts of the program?  Why or why not?</li>
<li>[2] Change the menu buttons so they are automatically generated from a (compile-time defined) data structure containing all of the images and their associated menu entries.  What looping construct did you use, and why?  Be sure to test your version with at least two additional images (so, the array should have at least four entries).
<li>[2] Add a double tap gesture that switches to the next available picture in the menu.  Use the data structure you created for the previous question.  Explain how you decided what the "next" image was.
<li>[8] Add TextFields to the bottom of the screen displaying the current X and Y position and the magnification factor (displayed as M) in a similar fashion to the existing one for degrees.  The X and Y should be displayed as whole numbers, while the magnification factor should be shown to two decimal places.  Each of these values should be editable, and when changes are made the image's state should be updated.  Be sure to show an explain each of the changes you made to the code. '''Note:''' ''It is okay if the position values are wrong when you first start the app, switch images, or reset the picture with a tap.''  (4 points for X and Y, 4 for magnification)</li>
</ol>
</ol>


==Code==
==Code==


[https://homeostasis.scs.carleton.ca/~soma/mad-2021w/code/1601picviewer-2.zip 1601picviewer-2.zip]
[https://homeostasis.scs.carleton.ca/~soma/mad-2021w/code/picviewer-2.zip picviewer-2.zip]


<syntaxhighlight lang="swift" line>
<syntaxhighlight lang="swift" line>
//
//
//  ContentView.swift
//  ContentView.swift
//  1601picviewer-2
//  picviewer-2
//  COMP 1601 Winter 2021, Carleton University
//  COMP 1601 Winter 2021, Carleton University
//
//
Line 37: Line 45:
     @State private var magnification: CGFloat = 1
     @State private var magnification: CGFloat = 1
     @State private var angleS = "0"
     @State private var angleS = "0"
 
   
     func resetState() {
     func resetState() {
         moved = false
         moved = false
Line 43: Line 51:
         angleS = "0"
         angleS = "0"
     }
     }
       
   
     var body: some View {
     var body: some View {
         VStack {
         VStack {
             Menu("Animals!") {
             Menu("Animals!") {
                    Button("Kittens", action: {
                Button("Kittens", action: {
                        theImage = "kittens"
                    theImage = "kittens"
                        resetState()
                    resetState()
                    })
                })
                    Button("Sad Dog", action: {
                Button("Sad Dog", action: {
                        theImage = "sadDog"
                    theImage = "sadDog"
                        resetState()
                    resetState()
                    })
                })
                }
            }
             ActiveImage(theImage: $theImage, moved: $moved, finalAmount: $magnification,
             ActiveImage(theImage: $theImage, moved: $moved, finalAmount: $magnification,
                         angleS: $angleS)
                         angleS: $angleS)
Line 73: Line 81:
     @State private var position = CGPoint(x: 0, y: 0)
     @State private var position = CGPoint(x: 0, y: 0)
     @State private var currentAmount: CGFloat = 0
     @State private var currentAmount: CGFloat = 0
 
   
     @Binding var theImage: String
     @Binding var theImage: String
     @Binding var moved: Bool
     @Binding var moved: Bool
Line 82: Line 90:
         GeometryReader {g in
         GeometryReader {g in
             Image(theImage)
             Image(theImage)
                    .resizable()
                .resizable()
                    .scaledToFit()
                .scaledToFit()
                    .position(moved ? position :
                .position(moved ? position :
                             CGPoint(x: g.size.width / 2, y: g.size.height / 2))
                             CGPoint(x: g.size.width / 2, y: g.size.height / 2))
                    .scaleEffect(finalAmount + currentAmount)
                .scaleEffect(finalAmount + currentAmount)
                    .rotationEffect(currentAngle())
                .rotationEffect(currentAngle())
                    .gesture(dragging)
                .gesture(dragging)
                    .gesture(SimultaneousGesture(rotating, magnifying))
                .gesture(SimultaneousGesture(rotating, magnifying))
                    .onTapGesture(count: 1, perform: tapReset)
                .onTapGesture(count: 1, perform: tapReset)
         }
         }
     }
     }
 
   
     func currentAngle() -> Angle {
     func currentAngle() -> Angle {
         if let a = Double(self.angleS) {
         if let a = Double(self.angleS) {
Line 107: Line 115:
         self.angleS = "0"
         self.angleS = "0"
     }
     }
       
   
     var rotating: some Gesture {
     var rotating: some Gesture {
         RotationGesture()
         RotationGesture()
Line 114: Line 122:
             }
             }
     }
     }
 
   
     var magnifying: some Gesture {
     var magnifying: some Gesture {
         MagnificationGesture().onChanged { amount in
         MagnificationGesture().onChanged { amount in
Line 138: Line 146:
}
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
</syntaxhighlight>
</syntaxhighlight>
==Solutions==
[https://homeostasis.scs.carleton.ca/~soma/mad-2021w/solutions/assign2-sol.txt Assignment 2 solutions]

Latest revision as of 16:11, 27 February 2021

Please answer all of the following questions in the supplied template, all of which are based on picviewer-2, the code for which is included below. There are six questions worth 20 points.

Do not turn modified applications. Instead, when answering code writing questions, include code snippets and explain how they should be used to modify the application. Be sure to use complete sentences when answering. Be sure to list collaborators and outside sources that you used.

Submit your answers via cuLearn by February 26, 2021 by 11:59 PM.

Questions

  1. [3] At the bottom of the screen it shows the current rotation of the image in degrees. This rotation information stored in a different format than it was in Tutorial 4's picviewer-1.
    1. [1] What is the difference?
    2. [2] Try changing picviewer-2 to store the rotation information the same way picviewer-1 did. What part is hard? Why? (Hint: Look at the documentation for TextField.) Note: You are not expected to fully implement this; instead, show what would have to be implemented and why this isn't so easy by referring to appropriate documentation or articles.
  2. [3] currentAngle() (lines 71-77):
    1. [1] What does currentAngle() do? (Explain what the function does in English.)
    2. [1] When is it called?
    3. [1] Replace the call to currentAngle() with an expression using the ?? operator (the "Nil-Coalescing Operator").
  3. [2] Change rotations so they are quantized to 45 degree increments during and at the end of each rotation gesture. It should still be possible to rotate to arbitrary angles by typing in an angle value. Did you have to change multiple parts of the program? Why or why not?
  4. [2] Change the menu buttons so they are automatically generated from a (compile-time defined) data structure containing all of the images and their associated menu entries. What looping construct did you use, and why? Be sure to test your version with at least two additional images (so, the array should have at least four entries).
  5. [2] Add a double tap gesture that switches to the next available picture in the menu. Use the data structure you created for the previous question. Explain how you decided what the "next" image was.
  6. [8] Add TextFields to the bottom of the screen displaying the current X and Y position and the magnification factor (displayed as M) in a similar fashion to the existing one for degrees. The X and Y should be displayed as whole numbers, while the magnification factor should be shown to two decimal places. Each of these values should be editable, and when changes are made the image's state should be updated. Be sure to show an explain each of the changes you made to the code. Note: It is okay if the position values are wrong when you first start the app, switch images, or reset the picture with a tap. (4 points for X and Y, 4 for magnification)

Code

picviewer-2.zip

//
//  ContentView.swift
//  picviewer-2
//  COMP 1601 Winter 2021, Carleton University
//

import SwiftUI

struct ContentView: View {
    @State private var theImage = "kittens"
    @State private var moved = false
    @State private var magnification: CGFloat = 1
    @State private var angleS = "0"
    
    func resetState() {
        moved = false
        magnification = 1
        angleS = "0"
    }
    
    var body: some View {
        VStack {
            Menu("Animals!") {
                Button("Kittens", action: {
                    theImage = "kittens"
                    resetState()
                })
                Button("Sad Dog", action: {
                    theImage = "sadDog"
                    resetState()
                })
            }
            ActiveImage(theImage: $theImage, moved: $moved, finalAmount: $magnification,
                        angleS: $angleS)
            HStack{
                Text("D:")
                TextField("", text: $angleS, onCommit: {
                    if Double(angleS) == nil {
                        angleS = "0"
                    }
                })
            }.padding()
        }
    }
}

struct ActiveImage: View {
    @State private var position = CGPoint(x: 0, y: 0)
    @State private var currentAmount: CGFloat = 0
    
    @Binding var theImage: String
    @Binding var moved: Bool
    @Binding var finalAmount: CGFloat
    @Binding var angleS: String
    
    var body: some View {
        GeometryReader {g in
            Image(theImage)
                .resizable()
                .scaledToFit()
                .position(moved ? position :
                            CGPoint(x: g.size.width / 2, y: g.size.height / 2))
                .scaleEffect(finalAmount + currentAmount)
                .rotationEffect(currentAngle())
                .gesture(dragging)
                .gesture(SimultaneousGesture(rotating, magnifying))
                .onTapGesture(count: 1, perform: tapReset)
        }
    }
    
    func currentAngle() -> Angle {
        if let a = Double(self.angleS) {
            return Angle(degrees: a)
        } else {
            return Angle(degrees: 0)
        }
    }
    
    func tapReset() {
        self.finalAmount = 1
        self.moved = false
        self.angleS = "0"
    }
    
    var rotating: some Gesture {
        RotationGesture()
            .onChanged { angle in
                self.angleS = String(format: "%.0f", angle.degrees)
            }
    }
    
    var magnifying: some Gesture {
        MagnificationGesture().onChanged { amount in
            self.currentAmount = amount - 1
        }
        .onEnded { amount in
            self.finalAmount += self.currentAmount
            self.currentAmount = 0
        }
    }
    
    var dragging: some Gesture {
        DragGesture()
            .onChanged {s in
                self.moved = true
                self.position = s.location
            }
            .onEnded {s in
                self.moved = true
                self.position = s.location
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Solutions

Assignment 2 solutions