页面 UIScrollview 一次一个项目

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

我正在尝试模仿 iOS 相机应用程序选项(视频、照片、肖像等)的滚动体验。滚动时,相机选项一次仅翻一页。

enter image description here

到目前为止,这就是我所拥有的。正如您在下面的演示中看到的,当用户滚动时,分页会停止在滚动视图边界的倍数上,而不是像相机应用程序那样。

class ViewController: UIViewController {
    lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 50
        return stackView
    }()
    
    lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        return scrollView
    }()
    
    let options = ["Time-lapse", "Slo-Mo", "Cinematic", "Video", "Photo", "Portrait", "Pano"]
    
    override func loadView() {
        let view = UIView()
        self.view = view
        view.backgroundColor = .white
        view.addSubview(scrollView)
        scrollView.addSubview(stackView)

        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.heightAnchor.constraint(equalToConstant: 50).isActive = true

        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
        
        for option in options {
            let label = UILabel()
            label.text = option.uppercased()
            stackView.addArrangedSubview(label)
        }
        
        scrollView.contentInset = UIEdgeInsets(top: 0, left: UIScreen.main.bounds.width/2, bottom: 0, right: UIScreen.main.bounds.width/2)
    }
}

enter image description here

我该如何解决这个问题?

ios swift uiscrollview scroll-paging
1个回答
0
投票

相机应用程序中的“选择面板”看起来不像使用

UIScrollView
...

请注意,我们不能拖动并滚动多个项目。

这是一种方法,使用

UIPanGestureRecognizer
UITapGetureRecognizer
...

我们会:

  • 创建一个
    UIView
    子类
  • 为每个选项创建标签
  • 将标签与约束“链接”在一起(而不是使用堆栈视图)
  • 为了将“选定”标签居中,我们可以将其 center-X 约束设置为自定义视图的 center-X,并设置动画

运行时会是这样的:

enter image description here


自定义

UIView
子类:

class SelectLabelPanelView: UIView {
    
    // so we can inform the controller that the selection changed
    public var callbackClosure: ((Int) -> ())?
    
    public var theLabelTitles: [String] = [] {
        didSet {
            for v in self.subviews {
                v.removeFromSuperview()
            }
            self.theLabels = []
            for str in theLabelTitles {
                let v = UILabel()
                v.text = str.uppercased()
                v.font = self.theNormalFont
                v.textColor = self.theNormalFontColor
                
                v.translatesAutoresizingMaskIntoConstraints = false
                self.addSubview(v)
                self.theLabels.append(v)
                
                v.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
                v.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
            }
            var prevV: UIView!
            for (i, v) in self.theLabels.enumerated() {
                if i > 0 {
                    v.leadingAnchor.constraint(equalTo: prevV.trailingAnchor, constant: self.spacing).isActive = true
                }
                prevV = v
            }
            self.centerConstraint = self.theLabels[self.selItem].centerXAnchor.constraint(equalTo: self.centerXAnchor)
            self.centerConstraint.isActive = true
            self.theLabels[self.selItem].font = self.theSelectedFont
            self.theLabels[self.selItem].textColor = self.theSelectedFontColor
        }
    }
    
    // public properties to set font, color and spacing
    public var theFont: UIFont = .systemFont(ofSize: 16.0) {
        didSet {
            self.theNormalFont = theFont
            // make selected font the same, but Bold
            var symTraits = theFont.fontDescriptor.symbolicTraits
            symTraits.insert([.traitBold])
            self.theSelectedFont = UIFont(descriptor: theFont.fontDescriptor.withSymbolicTraits(symTraits)!, size: theNormalFont.pointSize)
            self.updateLabels()
        }
    }
    public var theFontColor: UIColor = .white {
        didSet {
            self.theSelectedFontColor = theFontColor
            // make normal font color the same, but with 90% alpha
            self.theNormalFontColor = theFontColor.withAlphaComponent(0.9)
            self.updateLabels()
        }
    }
    
    // private properties with defaults
    private var theNormalFont: UIFont = .systemFont(ofSize: 16.0, weight: .regular)
    private var theSelectedFont: UIFont = .systemFont(ofSize: 16.0, weight: .bold)
    private var theNormalFontColor: UIColor = .white.withAlphaComponent(0.9)
    private var theSelectedFontColor: UIColor = .white
    
    // private vars
    private var theLabels: [UILabel] = []
    private var spacing: CGFloat = 24.0
    private var centerConstraint: NSLayoutConstraint!
    private var selItem: Int = 0
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        let pg = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        let tg = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        self.addGestureRecognizer(pg)
        self.addGestureRecognizer(tg)
    }
    
    @objc func handlePan(_ g: UIPanGestureRecognizer) {
        if g.state == .began {
            self.selectItem(g.velocity(in: self).x > 0 ? selItem - 1 : selItem + 1)
        }
    }
    @objc func handleTap(_ g: UITapGestureRecognizer) {
        let loc = g.location(in: self)
        // find the tapped label
        for i in 0..<self.theLabels.count {
            if self.theLabels[i].frame.contains(loc) {
                self.selectItem(i)
                break
            }
        }
    }
    private func selectItem(_ i: Int) {
        if i >= self.theLabels.count || i < 0 {
            return
        }
        self.selItem = i
        self.centerConstraint.isActive = false
        self.centerConstraint = theLabels[self.selItem].centerXAnchor.constraint(equalTo: self.centerXAnchor)
        self.centerConstraint.isActive = true
        UIView.animate(withDuration: 0.3, animations: {
            self.layoutIfNeeded()
        }, completion: { _ in
            self.updateLabels()
            self.callbackClosure?(self.selItem)
        })
    }
    private func updateLabels() {
        for v in self.theLabels {
            v.font = self.theNormalFont
            v.textColor = self.theNormalFontColor
        }
        self.theLabels[self.selItem].font = self.theSelectedFont
        self.theLabels[self.selItem].textColor = self.theSelectedFontColor
    }
    
    // so we can set the selected item from the controller
    public func setSelected(_ i: Int) {
        self.selectItem(i)
    }
}

示例视图控制器:

class LabelPanelVC: UIViewController {
    
    let panelView = SelectLabelPanelView()
    
    let options: [String] = ["Time-lapse", "Slo-Mo", "Cinematic", "Video", "Photo", "Portrait", "Pano"]
    let colors: [UIColor] = [.systemRed, .systemGreen, .systemBlue, .cyan, .yellow, .magenta, .systemBrown]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = colors[0]

        panelView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(panelView)
        
        NSLayoutConstraint.activate([
            panelView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
            panelView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            panelView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            panelView.heightAnchor.constraint(equalToConstant: 50),
        ])

        panelView.theLabelTitles = options
        
        panelView.callbackClosure = { [weak self] idx in
            guard let self = self else { return }
            
            // a label was selected... either
            //  tapped or
            //  dragged to the center
            
            // do something based on the selected index
            
            print("Item: \(idx) / \(options[idx]) was selected!")
            
            // make sure we don't exceed the colors bounds
            self.view.backgroundColor = self.colors[idx % self.colors.count]
        }
        
        // so we can see the panelView framing
        panelView.backgroundColor = .darkGray

        // we can change some default properties, if desired
        //panelView.theFont = .italicSystemFont(ofSize: 16.0)
        //panelView.theFontColor = .yellow
    }

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