在 UIScrollView 中仅水平缩放 UIView

Posted

技术标签:

【中文标题】在 UIScrollView 中仅水平缩放 UIView【英文标题】:Zoom only horizontally UIView in UIScrollView 【发布时间】:2019-03-29 08:10:01 【问题描述】:

我尝试了其他帖子中的一些解决方案,但我无法解决我要发布的问题。

我有一个绘制 SoundWave 的自定义 UIView,它包含一个 UIScrollView。 UIScrollView 包含一个主 UIView,其中包含另外两个自定义 UIView(标记左和右)和 UIImage 视图,我在其中渲染声波。

private func initialize()

    print("\(logClassName) initialize in Frame: \(frame)")

    //ScrollView
    addSubview(scrollView)
    scrollView.constraintToSuperViewEdges()


    //WaveView
    scrollView.addSubview(waveView)
    waveView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
    waveView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0).isActive = true
    waveView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0).isActive = true
    waveView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true

    waveViewWidthConstraint = waveView.widthAnchor.constraint(equalToConstant: 1000)
    NSLayoutConstraint.activate([waveViewWidthConstraint])
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant, height: 0)

    //SoundWaveImageView
    waveView.addSubview(soundWaveImageView)
    soundWaveImageView.constraintToSuperViewEdges()


    //WaveView markers
    //TimeMarker
    waveView.addSubview(timeMarkerView)
    timeMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    timeMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    timeMarkerView.widthAnchor.constraint(equalToConstant: 1).isActive = true
    timerMarkerViewLeadingContraint = timeMarkerView.leadingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 20)
    NSLayoutConstraint.activate([timerMarkerViewLeadingContraint])


    //LeftMarker
    waveView.addSubview(leftMarkerView)
    leftMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    leftMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    leftMarkerView.widthAnchor.constraint(equalToConstant: leftMarkerView.triangleWidth).isActive = true
    leftMarkerViewLeadingConstraint = leftMarkerView.leadingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 0)
    NSLayoutConstraint.activate([leftMarkerViewLeadingConstraint])


    //RightMarker
    waveView.addSubview(rightMarkerView)
    rightMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    rightMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    rightMarkerView.widthAnchor.constraint(equalToConstant: rightMarkerView.triangleWidth).isActive = true
    rightMarkerViewLeadingConstraint = rightMarkerView.trailingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 50)
    NSLayoutConstraint.activate([rightMarkerViewLeadingConstraint])


正如所见,主 UIView 是对 UIScrollView 边缘的约束,而宽度约束是根据需要更改的总长度。负责的函数如下:

private func updateWidthConstraintValue(_ newWidth:CGFloat)

    waveViewWidthConstraint.constant = newWidth
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant, height: 0)


当为 UIScrollView 实现缩放时,它可以工作,但它会垂直和水平缩放(如预期的那样),但我只想水平实现。到目前为止我有这个:

func scrollViewDidZoom(_ scrollView: UIScrollView) 

    print("\(logClassName) TEST -> scrollView did zoom \(scrollView.zoomScale)")
    expandHorizontally(withScale: scrollView.zoomScale)
    //scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0)
    //scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)


func viewForZooming(in scrollView: UIScrollView) -> UIView? 

    return waveView



private func expandHorizontally(withScale scale:CGFloat)

    let scaledWidth = currentWaveViewWidth * scale
    print("\(logClassName) TEST -> scaledWidth = \(scaledWidth)")

    updateWidthConstraintValue(scaledWidth)
    // Adjust content size and content offset
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant,
                                    height: 0);
    scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x,
                                       y: scrollView.contentOffset.y);


UI 变量是这样声明的:

lazy private (set) var scrollView:UIScrollView = 

    let rtView = UIScrollView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear

    rtView.bounces = false
    rtView.bouncesZoom = false

    rtView.minimumZoomScale = 1
    rtView.maximumZoomScale = 3

    rtView.delegate = self

    return rtView

()

private (set) lazy var waveView:UIView = 

    let rtView = UIView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear
    rtView.addGestureRecognizer(UITapGestureRecognizer(target: self,
                                                       action: #selector(waveViewDidTouch)))

    return rtView

()
private (set) var waveViewWidthConstraint = NSLayoutConstraint()

private (set) lazy var soundWaveImageView:UIImageView = 

    let rtView = UIImageView()

    rtView.translatesAutoresizingMaskIntoConstraints = false

    return rtView

()

private (set) lazy var timeMarkerView:UIView = 
    let rtView = UIView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = UIColor.AppColors.defaultTint

    return rtView
()
private var timerMarkerViewLeadingContraint = NSLayoutConstraint()

private (set) lazy var leftMarkerView:MarkerView = 

    let rtView = MarkerView(title: AppHelper.printLocalized(withKey: "messages.start", targetSpecific: false), markerDirection: .right)

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.delegate = self

    return rtView

()
private var leftMarkerViewLeadingConstraint = NSLayoutConstraint()

private (set) lazy var rightMarkerView:MarkerView = 

    let rtView = MarkerView(title: AppHelper.printLocalized(withKey: "messages.end", targetSpecific: false), markerDirection: .left)

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.delegate = self

    return rtView

()
private var rightMarkerViewLeadingConstraint = NSLayoutConstraint()

因为是一门大课,所以我尽量把所有相关信息都放上来。

【问题讨论】:

我没有检查您的所有代码,但防止 scrollView 垂直滚动的常用方法是将其内容约束到 scrollView 的父视图(顶部和底部锚点约束)。所以说你有 parentView > scrollView > contentsView :scrollView 应该被约束到 parentView 边缘,contentsView 应该被约束到 parentView 顶部和底部锚点。 感谢您的回复。我看到我只限制顶部并且我给出相等的高度。那么是否应该将相等的高度和约束移除到底部呢? 我猜是的...实际上 scrollView 应该被限制在 parentView 边缘,contentsView 应该被限制在 scrollView 边缘和 parentView 顶部和底部锚点。 那么,waveView(Markers 和 UIImageView 的包装)被限制在 scrollview 的边缘,并且限制在 scrollView 包装的顶部和底部,对吧?那么我对我的委托做些什么呢? 好吧,我的问题是关于缩放,所以它不起作用...... 【参考方案1】:

感谢 LaurentMaquet,我找到了一个临时解决方案。在某个时候,我会发布最终的 soundWaveView,因为它目前正在开发中。

所做的更改是:

1- 为 soundWave 创建一个自定义类。

class WaveView:UIView
    var unzoomedViewHeight: CGFloat = 0

    override var transform: CGAffineTransform
        get return super.transform 
        set
            var t = newValue
            t.d = 1.0
            t.ty = (1.0 - t.a) * unzoomedViewHeight/2
            super.transform = t
        
    

    override func layoutSubviews() 
        super.layoutSubviews()

        unzoomedViewHeight = frame.size.height
        print("\(logClassName) did layout to \(AppHelper.traitStatus) -> frame = \(frame)")

    


2- 让我的 soundView 变量成为 SoundView

private (set) lazy var waveView:WaveView = 

    let rtView = WaveView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear
    rtView.addGestureRecognizer(UITapGestureRecognizer(target: self,
                                                       action: #selector(waveViewDidTouch)))

    return rtView

()

结果

【讨论】:

以上是关于在 UIScrollView 中仅水平缩放 UIView的主要内容,如果未能解决你的问题,请参考以下文章

1-3 UIScrollView

图像缩放时在 UIScrollView 中停止滚动图像

缩放 UIScrollView 不起作用

SwiftUI/UIKit:水平滚动视图缩放视图

当我的 UIScrollView 缩放图像时,如何使框架正确居中?

如何在 UIScrollView 中仅禁用垂直滚动?