Mobile App Dev 2022W: Tutorial 4: Difference between revisions

From Soma-notes
No edit summary
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
'''This tutorial is still being developed.'''
In this tutorial you will be playing with [https://homeostasis.scs.carleton.ca/~soma/mad-2022w/code/remotePicViewer/ContentView.swift remotePicViewer], a simple image viewer in Swift that downloads images from a server.
In this tutorial you will be playing with [https://homeostasis.scs.carleton.ca/~soma/mad-2022w/code/remotePicViewer/ContentView.swift remotePicViewer], a simple image viewer in Swift that downloads images from a server.


Line 20: Line 18:
==Questions==
==Questions==


# Select different images from the menu.  Try different interactions with the image.  What can you do?  (Note that you can do pinch to zoomoperations in the simulator by holding down the option key and dragging the circles that appear.)
# Select different images from the menu.  Try different interactions with the image.  What can you do?  What actions are recognized?  (Note that you can do pinch to zoomoperations in the simulator by holding down the option key and dragging the circles that appear.)
# If you remove the VStack on line 16, does that change the program's behavior?  Why or why not?
# If you remove the VStack on line 29, does that change the program's behavior?  Why or why not?
# Why are so many state variables declared in ContentView but used in ActiveImage?  Why not keep them local to ActiveImage?  Explain for each parameter.
# Why are so many state variables declared in ContentView but used in ActiveImage?  Why not keep them local to ActiveImage?  Explain for each parameter.
# Why is the declaration on line 45 needed?  What does this tell you about the development of SwiftUI?
# Why is the moved variable needed?  Why not always just set the position to the position variable?
# Why is the moved variable needed?  Why not always just set the position to the position variable?
# What's the purpose of resetState()?
# What's the purpose of resetState()?
Line 28: Line 27:
==Tasks==
==Tasks==


# Change the rotation on each click to be 90 degrees.
# Change the program so that when you double tap it doubles the magnification of the image.
# Add an image to the menu.  Either do this by adding images from a new URL or by adding images to the app's assets and instead using Image (rather than AsyncImage).  Be sure to add that image to the menu so it can be selected like the others.  What did you have to do?
# Add an image to the menu.  Either do this by adding images from a new URL or by adding images to the app's assets and instead using Image (rather than AsyncImage).  Be sure to add that image to the menu so it can be selected like the others.  What did you have to do?
# 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.  '''Hint:''' Don't try to add and remove handlers when switching between modes, that won't work.  Instead, change how the magnifying and rotating handlers work.


==[https://homeostasis.scs.carleton.ca/~soma/mad-2022w/code/remotePicViewer/ContentView.swift Code: remotePicViewer ContentView.swift]==
==[https://homeostasis.scs.carleton.ca/~soma/mad-2022w/code/remotePicViewer/ContentView.swift Code: remotePicViewer ContentView.swift]==
Line 48: Line 48:
import SwiftUI
import SwiftUI


@available(macOS 12.0, *)
struct ContentView: View {
struct ContentView: View {
     @State private var theImage = "kittens"
     @State private var theImage = "kittens"

Latest revision as of 18:20, 2 February 2022

In this tutorial you will be playing with remotePicViewer, a simple image viewer in Swift that downloads images from a server.

As before, create a new project and replace ContentView.swift with the one for remotePicViewer. Build and run the program, then move on to the questions as tasks below.

Key Concepts & APIs

Questions

  1. Select different images from the menu. Try different interactions with the image. What can you do? What actions are recognized? (Note that you can do pinch to zoomoperations in the simulator by holding down the option key and dragging the circles that appear.)
  2. If you remove the VStack on line 29, does that change the program's behavior? Why or why not?
  3. Why are so many state variables declared in ContentView but used in ActiveImage? Why not keep them local to ActiveImage? Explain for each parameter.
  4. Why is the declaration on line 45 needed? What does this tell you about the development of SwiftUI?
  5. Why is the moved variable needed? Why not always just set the position to the position variable?
  6. What's the purpose of resetState()?

Tasks

  1. Change the rotation on each click to be 90 degrees.
  2. Change the program so that when you double tap it doubles the magnification of the image.
  3. Add an image to the menu. Either do this by adding images from a new URL or by adding images to the app's assets and instead using Image (rather than AsyncImage). Be sure to add that image to the menu so it can be selected like the others. What did you have to do?

Code: remotePicViewer ContentView.swift

// remotePicDemo
//
// code for Tutorial 4
// COMP 1601 2022W, Carleton University
//
// Anil Somayaji, February 1, 2022
// Code is licenced under the FSF GPLv3 or newer
//
// Note this code is for teaching purposes only.  In particular
// it is not robust and does not handle error conditions properly.
//

import SwiftUI

@available(macOS 12.0, *)
struct ContentView: View {
    @State private var theImage = "kittens"
    @State private var moved = false
    @State private var finalAmount: CGFloat = 1
    @State private var angle = Angle(degrees: 0.0)

    func resetState() {
        moved = false
        finalAmount = 1
        angle = Angle(degrees: 0.0)
    }
    
    var body: some View {
        VStack {
            Menu("Animals!") {
                    Button("Kittens", action: {
                        theImage = "kittens"
                        resetState()
                    })
                    Button("Sad Dog", action: {
                        theImage = "roshi"
                        resetState()
                    })
                }
            ActiveImage(theImage: $theImage, moved: $moved, finalAmount: $finalAmount, angle: $angle)
        }
    }
}

@available(macOS 12.0, *)
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 angle: Angle
    
    let serverPrefix = "https://homeostasis.scs.carleton.ca/~soma/mad-2022w/images/"
    var body: some View {
        GeometryReader {g in
            AsyncImage(url: URL(string: serverPrefix + theImage + ".jpeg"), content: {image in
                image
                    .resizable()
                    .scaledToFit()
                    .position(moved ? position :
                                CGPoint(x: g.size.width / 2,
                                        y: g.size.height / 2))
                    .scaleEffect(finalAmount + currentAmount)
                    .gesture(dragging)
                    .gesture(magnifying)
                    .rotationEffect(self.angle)
                    .onLongPressGesture(perform: tapReset)
                    .onTapGesture(count: 1, perform: rotateImage)
            }, placeholder: {
                ProgressView()
                    .position(moved ? position :
                                CGPoint(x: g.size.width / 2,
                                        y: g.size.height / 2))
            })
        }
    }

    func tapReset() {
        self.finalAmount = 1
        self.moved = false
        self.angle = Angle(degrees: 0.0)
    }
    
    func rotateImage() {
        let oldDegrees = self.angle.degrees
        self.angle = Angle(degrees: oldDegrees + 45)
    }

    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()
    }
}