动态地找到合适的缩放比例以适应视图的一部分。

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

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

UIScrollView
-- UIView
---- [UIViews]

这是一张截图。

enter image description here

知道一个瓷砖的宽度和高度为 tileSide我怎样才能找到一种方法,以蓝色边框为焦点,进行程序化放大?我基本上需要找到正确的 zoomScale.

我正在做的是:

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

哪儿 actualGridWidth 定义为 tileSide 乘以列数。我所得到的是看到近7块瓷砖,而不是我想看到的5块。

还需要注意的是 contentView (棕色的那个)有一个完整的屏幕框架,就像它所包含的滚动视图。

ios swift uiscrollview zoom
1个回答
1
投票

你可以用 zoom(to rect: CGRect, animated: Bool) (苹果文档).

  • 获取左上角和右下角瓷砖的框架。
  • 然后转换为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
}

开始时的样子是这样的。

enter image description here

你点击的第一个 "瓷砖 "会变成绿色。

enter image description here

当你点击第二块瓷砖时,它会变成红色 我们会放大到那个矩形。

enter image description here

第三次点击就会恢复到起始网格

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