【问题标题】:Dynamically find the right zoom scale to fit portion of view动态找到合适的缩放比例以适合视图的一部分
【发布时间】:2023-03-26 09:18:01
【问题描述】:

我有一个网格视图,它就像一个棋盘。层次结构是这样的:

UIScrollView
-- UIView
---- [UIViews]

这是截图。

知道图块的宽度和高度为tileSide,我怎样才能找到一种方法以编程方式放大关注蓝色边框的区域?我基本上需要找到合适的zoomScale

我正在做的是:

let centralTilesTotalWidth = tileSide * 5
zoomScale = CGFloat(centralTilesTotalWidth) / CGFloat(actualGridWidth) + 1.0

其中actualGridWidth 定义为tileSide 乘以列数。我得到的是看到几乎七个瓷砖,而不是我想看到的五个。

同时保证contentView(棕色)有一个全屏框架,就像它所在的滚动视图一样。

【问题讨论】:

    标签: ios swift uiscrollview zooming


    【解决方案1】:

    您可以使用zoom(to rect: CGRect, animated: Bool) (Apple docs) 来执行此操作。

    • 获取左上角和右下角图块的框架
    • 然后转换为 contentView 坐标
    • 联合两个矩形
    • 致电zoom(to:...)

    这是一个完整的示例 - 全部通过代码,没有 @IBOutlet@IBAction 连接 - 所以只需创建一个新的视图控制器并将其自定义类分配给 GridZoomViewController

    class GridZoomViewController: UIViewController, UIScrollViewDelegate {
    
        let scrollView: UIScrollView = {
            let v = UIScrollView()
            return v
        }()
    
        let contentView: UIView = {
            let v = UIView()
            return v
        }()
    
        let gridStack: UIStackView = {
            let v = UIStackView()
            v.axis = .vertical
            v.distribution = .fillEqually
            return v
        }()
    
        var selectedTiles: [TileView] = [TileView]()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            [gridStack, contentView, scrollView].forEach {
                $0.translatesAutoresizingMaskIntoConstraints = false
            }
    
            var bColor: Bool = false
    
            // create a 9x7 grid of tile views, alternating cyan and yellow
            for _ in 1...7 {
                // horizontal stack view
                let rowStack = UIStackView()
                rowStack.translatesAutoresizingMaskIntoConstraints = false
                rowStack.axis = .horizontal
                rowStack.distribution = .fillEqually
                for _ in 1...9 {
                    // create a tile view
                    let v = TileView()
                    v.translatesAutoresizingMaskIntoConstraints = false
                    v.backgroundColor = bColor ? .cyan : .yellow
                    v.origColor = v.backgroundColor!
                    bColor.toggle()
                    // add a tap gesture recognizer to each tile view
                    let g = UITapGestureRecognizer(target: self, action: #selector(self.tileTapped(_:)))
                    v.addGestureRecognizer(g)
                    // add it to the row stack view
                    rowStack.addArrangedSubview(v)
                }
                // add row stack view to grid stack view
                gridStack.addArrangedSubview(rowStack)
            }
    
            // add subviews
            contentView.addSubview(gridStack)
            scrollView.addSubview(contentView)
            view.addSubview(scrollView)
    
            let padding: CGFloat = 20.0
    
            // respect safe area
            let g = view.safeAreaLayoutGuide
    
            // for scroll view content constraints
            let cg = scrollView.contentLayoutGuide
    
            // let grid width shrink if 7:9 ratio is too tall for view
            let wAnchor = gridStack.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0)
            wAnchor.priority = .defaultHigh
    
            NSLayoutConstraint.activate([
    
                // constrain scroll view to view (safe area), all 4 sides with "padding"
                scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: padding),
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: padding),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -padding),
                scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -padding),
    
                // constrain content view to scroll view contentLayoutGuide, all 4 sides
                contentView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
                contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
                contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
                contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
    
                // content view width and height equal to scroll view width and height
                contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: 0.0),
                contentView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor, constant: 0.0),
    
                // activate gridStack width anchor
                wAnchor,
    
                // gridStack height = gridStack width at 7:9 ration (7 rows, 9 columns)
                gridStack.heightAnchor.constraint(equalTo: gridStack.widthAnchor, multiplier: 7.0 / 9.0),
    
                // make sure gridStack height is less than or equal to content view height
                gridStack.heightAnchor.constraint(lessThanOrEqualTo: contentView.heightAnchor),
    
                // center gridStack in contentView
                gridStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0.0),
                gridStack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0),
    
            ])
    
            // so we can see the frames
            view.backgroundColor = .blue
            scrollView.backgroundColor = .orange
            contentView.backgroundColor = .brown
    
            // delegate and min/max zoom scales
            scrollView.delegate = self
            scrollView.minimumZoomScale = 0.25
            scrollView.maximumZoomScale = 5.0
    
        }
    
        func viewForZooming(in scrollView: UIScrollView) -> UIView? {
            return contentView
        }
    
        override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
            coordinator.animate(alongsideTransition: nil, completion: {
                _ in
                if self.selectedTiles.count == 2 {
                    // re-zoom the content on size change (such as device rotation)
                    self.zoomToSelected()
                }
            })
        }
    
        @objc
        func tileTapped(_ gesture: UITapGestureRecognizer) -> Void {
    
            // make sure it was a Tile View that sent the tap gesture
            guard let tile = gesture.view as? TileView else { return }
    
            if selectedTiles.count == 2 {
                // if we already have 2 selected tiles, reset everything
                reset()
            } else {
                // add this tile to selectedTiles
                selectedTiles.append(tile)
                // if it's the first one, green background, if it's the second one, red background
                tile.backgroundColor = selectedTiles.count == 1 ? UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0) : .red
                // if it's the second one, zoom
                if selectedTiles.count == 2 {
                    zoomToSelected()
                }
            }
    
        }
    
        func zoomToSelected() -> Void {
            // get the stack views holding tile[0] and tile[1]
            guard let sv1 = selectedTiles[0].superview,
                let sv2 = selectedTiles[1].superview else {
                    fatalError("problem getting superviews! (this shouldn't happen)")
            }
            // convert tile view frames to content view coordinates
            let r1 = sv1.convert(selectedTiles[0].frame, to: contentView)
            let r2 = sv2.convert(selectedTiles[1].frame, to: contentView)
            // union the two frames to get one larger rect
            let targetRect = r1.union(r2)
            // zoom to that rect
            scrollView.zoom(to: targetRect, animated: true)
        }
    
        func reset() -> Void {
            // reset the tile views to their original colors
            selectedTiles.forEach {
                $0.backgroundColor = $0.origColor
            }
            // clear the selected tiles array
            selectedTiles.removeAll()
            // zoom back to full grid
            scrollView.zoom(to: scrollView.bounds, animated: true)
        }
    }
    
    class TileView: UIView {
        var origColor: UIColor = .white
    }
    

    开始时看起来像这样:

    您点击的第一个“图块”将变为绿色:

    当您点击第二个图块时,它会变为红色,我们将放大该矩形:

    点击第三次将重置为起始网格。

    【讨论】:

    • 这是一个非常有用的解决方案!关键是两个矩形的结合。恭喜!
    猜你喜欢
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-02
    • 2012-02-05
    • 1970-01-01
    相关资源
    最近更新 更多