使用 .digitalCrownRotation() 修饰符时在 ScrollView 中禁用数字表冠滚动

问题描述 投票:0回答:1

我正在开发一个 SwiftUI 修改器 (

ZoomableModifier
),它将主要用于使图像可缩放。我希望它可以在 iOS 和 watchOS 上运行,特别是在 watchOS 上。我希望能够使用数字表冠进行放大和缩小,为了实现这一点,我使用
.digitalCrownRotation()
修改器来读取数字表冠的旋转并使用它来确定比例因子。

这是修改器的代码:

//
//  ZoomableModifier.swift
//
//  Created by jasu on 2022/01/26.
//  Modified by Toni Sucic on 2023/01/08.
//  Copyright (c) 2022 jasu All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is furnished
//  to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
//  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
//  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
//  OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import SwiftUI

public struct ZoomableModifier: ViewModifier {

    private enum ZoomState {
        case inactive
        case active(scale: CGFloat)

        var scale: CGFloat {
            switch self {
            case .active(let scale):
                return scale
            default:
                return 1.0
            }
        }
    }

    /// The content size of the views.
    let contentSize: CGSize
    /// The minimum value that can be zoom out.
    let min: CGFloat
    /// The maximum value that can be zoom in.
    let max: CGFloat
    /// A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
    let showsIndicators: Bool

#if os(iOS)
    @GestureState private var zoomState = ZoomState.inactive
#endif
    @State private var currentScale: CGFloat = 1.0

    var scale: CGFloat {
#if os(iOS)
        currentScale * zoomState.scale
#elseif os(watchOS)
        currentScale
#endif
    }

#if os(iOS)
    var zoomGesture: some Gesture {
        MagnificationGesture()
            .updating($zoomState) { value, state, transaction in
                state = .active(scale: value)
            }
            .onEnded { value in
                var new = currentScale * value
                if new <= min { new = min }
                if new >= max { new = max }
                currentScale = new
            }
    }
#endif

    var doubleTapGesture: some Gesture {
        TapGesture(count: 2).onEnded {
            if scale <= min { currentScale = max } else
            if scale >= max { currentScale = min } else {
                currentScale = ((max - min) * 0.5 + min) < scale ? max : min
            }
        }
    }

    public func body(content: Content) -> some View {
        ScrollView([.vertical, .horizontal], showsIndicators: showsIndicators) {
            content
                .frame(width: contentSize.width * scale, height: contentSize.height * scale, alignment: .center)
                .scaleEffect(scale, anchor: .center)
#if os(watchOS)
                .focusable(true)
                .digitalCrownRotation(detent: $currentScale,
                                      from: min,
                                      through: max,
                                      by: 0.1,
                                      sensitivity: .low,
                                      isContinuous: false,
                                      isHapticFeedbackEnabled: true)
#endif
        }
#if os(iOS)
        .gesture(ExclusiveGesture(zoomGesture, doubleTapGesture))
#elseif os(watchOS)
        .gesture(doubleTapGesture)
#endif
        .animation(.easeInOut, value: scale)
    }
}

extension View {
    func zoomable(contentSize: CGSize,
                  min: CGFloat = 1.0,
                  max: CGFloat = 3.0,
                  showsIndicators: Bool = false) -> some View {
        modifier(ZoomableModifier(contentSize: contentSize, min: min, max: max, showsIndicators: showsIndicators))
    }
}

我在此视图中使用修改器:

struct FullscreenImage: View {
    let imageURL: URL

    @Environment(\.dismiss) var dismiss

    var body: some View {
        GeometryReader { proxy in
            AsyncImage(url: imageURL) { imagePhase in
                Group {
                    switch imagePhase {
                    case .success(let image):
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: proxy.size.width, height: proxy.size.height)
                            .zoomable(contentSize: proxy.size)
                    case .failure(let error):
                        Text(error.localizedDescription)
                            .font(.caption)
                            .padding()
                    default:
                        Rectangle()
                            .fill(Color.white.opacity(0.2))
                    }
                }
                .animation(.easeInOut, value: imagePhase)
            }
        }
    }
}

我希望有一种方法可以在使用

.digitalCrownRotation()
修饰符时禁用滚动视图的数字表冠滚动,但我还没有找到。因此,当我旋转数码表冠时,它会尝试滚动滚动视图,而不是更改比例因子来获得我正在寻找的缩放效果。

swiftui scrollview zooming watchos
1个回答
0
投票

我通过用 VStack 替换 ScrollView 来实现您想要的功能(在不同的上下文中),然后将数字表冠读数附加到我想要缩放的视图的比例,然后将 .gesture 修改器附加到视图并将其提供给.offset 修饰符的 x 和 y 值,附加到嵌入视图(如果您需要滚动效果)。

© www.soinside.com 2019 - 2024. All rights reserved.