【问题标题】:Swift Set Scrollview Subviews Frame Location based off content offsetSwift Set Scrollview Subviews Frame Location based off content offset
【发布时间】:2021-01-17 03:04:12
【问题描述】:

问题:

我正在重新创建 iPhone App Switcher 页面,其中应用视图在屏幕左侧相互重叠。看起来每个应用视图框架的 x 位置是根据它在可见屏幕上的索引和位置设置的。

我在滚动视图中有一组视图。您将如何设置视图框架以复制 iPhone 应用切换页面?

尝试:

`func scrollViewDidScroll(_ scrollView: UIScrollView) {
    items.enumerated().forEach { (index, tabView) in
        let screenWidth = UIScreen.main.bounds.width
        
        // This returns a value between 0 and 1 depending on the location of the tab view within the visible screen
        let xOffset = scrollView.convert(CGPoint(x: tabView.frame.minX, y: 0), to: view).x
        let percentViewMovedOnVisibleScreen: CGFloat = xOffset / screenWidth
        
        // Spacing - NOT CORRECT
        let someSpacingAmount: CGFloat = 80
        tabView.frame.origin.x = CGFloat(index) * percentViewMovedOnVisibleScreen * someSpacingAmount
    }
}`

我认为这很接近,但它并不能完全让您了解 Apple 所拥有的。也许需要 didScroll 以外的其他东西才能让它感觉流畅?

GitHub 示例项目https://github.com/Alexander-Frost/ViewContentOffset

【问题讨论】:

  • scrollViewDidScroll 可以使用。但是你必须获得适当的动画。在 AppSwitcher 中有几种不同的动画,当应用程序到达屏幕中心时,它的行为不同,它会变慢,最后它会缩小一点并淡化。因此,您将需要处理所有这些情况并为动画的不同阶段计算不同的帧。应用程序框架也是一个在彼此之上,不像在您的示例中具有水平间距。而且它们仅在最后才真正扩展。因此,由于速度变化,这一切看起来都不错
  • @GrzegorzKrukowski 是的,它需要分成几部分。知道如何实现根据屏幕位置变化的间距吗?

标签: swift uiscrollview


【解决方案1】:

这很可能(很可能,我会说)这不是在滚动视图中开始的。

考虑这个例子:

class TestSwitcherViewController: UIViewController {
    
    var leadConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let colors: [UIColor] = [
            // light red
            UIColor(red: 1.0, green: 0.5, blue: 0.5, alpha: 1.0),
            // light green
            UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0),
            // light blue
            UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0),
            // light orange
            UIColor(red: 0.9, green: 0.7, blue: 0.5, alpha: 1.0),
            // yellow
            UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0),
        ]
        
        var prevView: UIView!
        
        // create a view for each color (a label with the view number as its text)
        // for each of the views
        //  if it's the first one,
        //      constrain leading to view leading
        //  else
        //      constrain leading to previous view leading, constant 1.0, multiplier 2.0

        var i = 1
        colors.forEach { c in
            let v = UILabel()
            v.backgroundColor = c
            v.text = "\(i)"
            v.textAlignment = .center
            v.layer.cornerRadius = 20
            v.layer.masksToBounds = true
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            v.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
            v.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
            v.heightAnchor.constraint(equalToConstant: 400.0).isActive = true
            if i == 1 {
                leadConstraint = v.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0)
                leadConstraint.isActive = true
            } else {
                NSLayoutConstraint(item: v, attribute: .leading, relatedBy: .equal, toItem: prevView, attribute: .leading, multiplier: 2.0, constant: 1.0).isActive = true
            }
            prevView = v
            i += 1
        }
        
        // add a pan gesture recognizer to the view
        let pan = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
        view.addGestureRecognizer(pan)
        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateScales()
    }
    
    @objc func didPan(_ gesture: UIPanGestureRecognizer) -> Void {
        
        let translation = gesture.translation(in: view)
        
        // increment or decrement the leading anchor constant
        let tmpX = leadConstraint.constant + (translation.x * 0.25)

        // don't let it go past either side
        leadConstraint.constant = min(max(tmpX, 0.0), view.frame.width - 200.0)
        
        gesture.setTranslation(.zero, in: view)

        updateScales()
        
    }
    
    func updateScales() -> Void {
        view.subviews.forEach { v in
            // percentage of distance from leading edge of label to 1/5th width of view
            let pct = min(v.frame.origin.x / (view.frame.width * 0.2), 1.0)
            let scale = 0.8 + 0.2 * pct
            v.transform = .identity
            v.transform = CGAffineTransform(scaleX: scale, y: scale)
        }
    }
    
}

它创建 5 个彩色视图(编号标签),并使用具有常量和乘数的前导锚将它们相互约束。它向视图添加了平移手势,因此当您向左或向右平移时,它会修改“底部”视图的前导约束的常量以将其向左/向右移动。这反过来又会移动其他视图...并且由于我们使用了一个乘数作为约束,所以当我们向右滑动时,移动会“增长”。

这是它在发布时的样子:

这是向右拖动一点后的样子:

显然,要复制 App Switcher 的所有功能需要做更多的工作,但它可能会让您顺利上路。

【讨论】:

  • Apple 肯定在使用滚动视图。他们使用减速方法总是降落在一个视图上。在滚动视图中,您将如何更改视图的 x 框架,类似于您使用约束的方式?
  • @Ryan - 无需滚动视图即可轻松将视图动画化到中心。正如我所说,会有更多的工作......这只是以相对速度移动视图的一种方法。您可能想看看iCarousel ...“时间机器”效果并不遥远。检查该代码可能会给您一些启发。
猜你喜欢
  • 1970-01-01
  • 2022-12-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 1970-01-01
  • 2012-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多