4

I want to make the same thing that is possible to do with a slider:

@State itemSize: CGFloat = 55.60

....

Text("ItemSize: \(itemSize)")
Slider(value: $itemSize, in: 45...120)

but with magnification gesture.

I have created modifier for this:

import SwiftUI

@available(OSX 11.0, *)
public extension View {
    func magnificationZoomer(value: Binding<CGFloat>,min: CGFloat, max: CGFloat) -> some View
    {
         self.modifier( MagnificationZoomerMod(minZoom: min, maxZoom: max, zoom: value) )
    }
}

@available(OSX 11.0, *)
public struct MagnificationZoomerMod: ViewModifier {
    let minZoom: CGFloat
    let maxZoom: CGFloat
    @Binding var zoom: CGFloat
    
    public func body (content: Content) -> some View
    {
        content
            .gesture(
                MagnificationGesture()
                    .onChanged() { val in
                        let magnification = (val - 1) * 2.2
                        
                        print("magnification = \(magnification)")
                        
                        zoom = max(min(zoom + magnification, maxZoom), minZoom)
                    }
            )
    }
}

sample of usage:

@State itemSize: CGFloat = 55.60

var body: some View {
    HStack {
       VStack {
           Text("ItemSize: \(itemSize)")
           Slider(value: $itemSize, in: 45...120)
       }
    }
    .frame(width: 500, height: 500)
    .magnificationZoomer(value: $itemSize, min: 45, max: 120)
}

But I have a few problems with this code:

  1. It slide value by a strange way -- sometimes with correct speed, it does not change it
  2. after some time of usage it stop to work at all

What did I do wrong?

Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101

1 Answers1

5

It works almost like a magic, you can change the size/scale of Circle via Slider or your finger on MagnificationGesture, both working together and sinked together.


enter image description here


enter image description here


import SwiftUI

struct ContentView: View {
    
    let minZoom: CGFloat = 0.5
    let maxZoom: CGFloat = 1.5
    
    @State private var scale: CGFloat = 1.0
    @State private var lastScale: CGFloat = 1.0
    @State private var magnitudeIsActive: Bool = Bool()
    
    var body: some View {
        
        ZStack {
            
            Circle()
                .fill(Color.red)
                .scaleEffect(scale)
                .gesture(magnificationGesture.simultaneously(with: dragGesture)) // <<: Here: adding unneeded dragGesture! on macOS! no need on iOS!
            
            VStack {
                
                Spacer()
                
                Text("scale: " + scale.rounded)
                
                HStack {
                    
                    Button("min") { scale = minZoom; lastScale = scale }
                    
                    Slider(value: Binding.init(get: { () -> CGFloat in return scale },
                                               set: { (newValue) in if !magnitudeIsActive { scale = newValue; lastScale = newValue } }), in: minZoom...maxZoom)

                    Button("max") { scale = maxZoom; lastScale = scale }
                }
                
            }
            
        }
        .padding()
        .compositingGroup()
        .shadow(radius: 10.0)
        .animation(Animation.easeInOut(duration: 0.2), value: scale)

        
    }

    var magnificationGesture: some Gesture {
        
        MagnificationGesture(minimumScaleDelta: 0.0)
            .onChanged { value in
                
                if !magnitudeIsActive { magnitudeIsActive = true }
                
                let magnification = (lastScale + value.magnitude - 1.0)
                
                if (magnification >= minZoom && magnification <= maxZoom) {
                    
                    scale = magnification
                    
                }
                else if (magnification < minZoom) {
                    
                    scale = minZoom
                }
                else if (magnification > maxZoom) {
                    
                    scale = maxZoom
                }
                
            }
            .onEnded { value in
                
                let magnification = (lastScale + value.magnitude - 1.0)
                
                if (magnification >= minZoom && magnification <= maxZoom) {
                    
                    lastScale = magnification
                    
                }
                else if (magnification < minZoom) {
                    
                    lastScale = minZoom
                }
                else if (magnification > maxZoom) {
                    
                    lastScale = maxZoom
                }
                
                scale = lastScale
                magnitudeIsActive = false
                
            }
        
    }
    
    
    var dragGesture: some Gesture { DragGesture(minimumDistance: 0.0) } // <<: Here: this Extra un-needed gesture would keep magnificationGesture alive! And Stop it to get killed in macOS! We do not need this line of code in iOS!
    
}

extension CGFloat { var rounded: String { get { return String(Double(self*100.0).rounded()/100.0) } } }
ios coder
  • 1
  • 4
  • 31
  • 91
  • Problems of the code: 1. After some time of usage on macOS it is stop to work - try it; 2. if I will scale down to the minimum and will try to scale-up with this code - it will start from 1.0 value -- try to set 0.45 and after to set 0.8 with gesture; 3. try to set 0.45 with gesture - in part of situations it doesn't work and will stop on 0.47; 046 or 0.50; – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 09:08
  • 1
    I can fix all with new update, but I am not sure about the issue with macOS – ios coder Apr 09 '21 at 09:27
  • 1
    I just solved all issue together, the reason of them was Slider! **Slider** and **magnificationGesture** were in competition with together to update scale in last version, in new update that issue gone plus some other fix and update done. – ios coder Apr 09 '21 at 11:18
  • 1
    will test a little bit and mark as solved. Thanks a lot for your help – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 11:27
  • Hah, this code have a one more problem)) If we scale to max value, and will try to scale-down we will have no ability to do this. We need to stop gesture and start a new one. But I think I will find solution for this by my own hands. Really thanks a lot for your assistance. – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 11:45
  • you scale with gesture or with Slider? I tried both way working fine. can you give more info for create issue? I think your interaction with gesture is wrong, I mean the way that apple device accept 2 finger gesture from user side or how we should work with zoom in and zoom out from user side. – ios coder Apr 09 '21 at 11:52
  • I have deleted slider for more clear tests. So I have tested with gesture only. MacOs BigSur 11.0.1. | Xcode Version 12.4 (12D4e). I will write you later with details after some code modifications. – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 11:55
  • 1
    No, bug is little bit another... But I think this is problem of macOS or SwiftUI 2 for MacOS builds. I believe your code must works well. Once more: thanks a lot. I will award your answer when this will be possible – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 12:02
  • @Andrew: the issue is coming from Slider in macOS version, in iOS it works fine, but in macOS is the reason of issue! the gesture works fine without Slider in macOS, but with Slider stop working after some min for unknown reason for me! I would say you should make a custom Slider, which has no issue, I never use apple Slider for this reasons! – ios coder Apr 09 '21 at 13:00
  • I'm testing without slider :) So reason is some another :) I will give you my verstion of your code + video with testing a little bit later – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 13:18
  • 1
    No need for Video, I was asked for that because I was unable to face with issue, but now I know about it, what ever is cause the issue it make gesture completely disable! like we never used or applied that gesture to code! that unknown bug or issue make gesture disconnect from our app! I do not have any salvation for that. maybe a bug from Xcode or from macOS, no idea. – ios coder Apr 09 '21 at 13:29
  • 1
    @Andrew: I wanted add this also, the issue that we are facing in macOS is not in our side as programmer, because if we had bad or wrong code, the Xcode would send a massage in console to inform us, our we would see some weird scale change like you mentioned in your first try of codes, we are not facing this kind of issues, our gesture get killed completely after some min working fine, like we had no gesture as before! And that is why I believe it is out of fix for us. – ios coder Apr 09 '21 at 14:56
  • @Andrew: I found a silly hack for that issue! It fixed the issue for me, I want see if it works for you as well, I updated the codes. – ios coder Apr 09 '21 at 15:21
  • still is buggy and not stable. But looks like it do not do fully stop of work right now. I think that easiest way to get stable correct behavior is to write own wrapper around old UI system.... =( – Andrew_STOP_RU_WAR_IN_UA Apr 09 '21 at 16:01
  • I think as like you as well about using old UI, because gestures are not new in macOS, there where there for long time, this is Xcode or SwiftUI which make issue to compile right code for macOS. – ios coder Apr 09 '21 at 16:07