UIScrollView 中的 UIPageViewController 显示全屏,即使设置了 CGRect - returns 以在用户滚动时立即更正帧值

UIPageViewController within a UIScrollView appears fullscreen even though CGRect is set - returns to correct frame values as soon as user scrolls

我有一个名为 SwipingPhotosControllerUIPageViewController,用户可以在其中水平滑动以查看图像。我已经在 SwipingPhotosController 中实现了这个 UIScrollView 通过给它 CGRect 值。然后我编写了一个函数,当用户向上或向下滚动时,它基本上可以实现放大和缩小的 弹性 header 效果

一切正常,除了当我尝试 SwipingPhotosController 下添加另一个视图时,一旦加载控制器,图像出现全屏。只要我稍微滚动一下,它就会 returns 到准确的位置。

这是我在模拟器中按 运行 时得到的结果 - 全屏显示 pageviewcontroller:

稍微滚动一下,视图就会恢复正常

注意:仅当我在 imageView 下方添加 nameLabel 时才会出现此错误 (swipingPhotosController.view)

class ProfileController: UIViewController, UIScrollViewDelegate {

    var user: User! {
        didSet{
            swipingPhotosController.user = user
        }
    }

    lazy var scrollProfileView: UIScrollView = {
        let sv = UIScrollView()
        sv.delegate = self
        sv.backgroundColor = TDGSettings
        sv.alwaysBounceVertical = true
        sv.contentInsetAdjustmentBehavior = .never
        return sv
    }()

    let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 12, weight: .bold)
        label.textColor = .white
        label.numberOfLines = 2
        label.textAlignment = .left
        return label
    }()

    let swipingPhotosController = SwipingPhotosController(transitionStyle: .scroll, navigationOrientation: .horizontal)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = TDGSettings
        setupViews()
    }

    fileprivate func setupViews() {
        view.addSubview(scrollProfileView)
        scrollProfileView.fillSuperview()

        let imageView = swipingPhotosController.view!
        scrollProfileView.addSubview(imageView)

        let blurEffect = UIBlurEffect(style: .systemThinMaterialDark)
        let visualEffectView = UIVisualEffectView(effect: blurEffect)
        view.addSubview(visualEffectView)
        visualEffectView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.topAnchor, trailing: view.trailingAnchor)

        scrollProfileView.addSubview(nameLabel)
        nameLabel.anchor(top: imageView.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 16, left: 16, bottom: 0, right: 16))
        nameLabel.text = "First Name"
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        let imageView = swipingPhotosController.view!
        imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
    }

    //STRETCHY HEADER EFFECT
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let changeY = -scrollView.contentOffset.y
        var width = view.frame.width + changeY * 2
        width = max(view.frame.width, width)
        let imageView = swipingPhotosController.view!
        imageView.frame = CGRect(x: min(0, -changeY), y: min(0, -changeY), width: width, height: width)
    }
}

根据要求为 SwipingPhotosController 添加代码

import Foundation
import LBTATools

class SwipingPhotosController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var user: User! {
        didSet{
            controllers = user.swipingUrls.map({ (url) -> UIViewController in
                let photoController = PhotosController(imageUrl: url)
                return photoController
            })
            setViewControllers([controllers.first!], direction: .forward, animated: false, completion: nil)
        }
    }
    var controllers = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = self
        delegate = self
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let index = self.controllers.firstIndex(where: {[=12=] == viewController}) ?? 0
        if index == 0 {return nil}
        return controllers[index - 1]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let index = self.controllers.firstIndex(where: {[=12=] == viewController}) ?? 0
        if index == controllers.count - 1 {return nil}
        return controllers[index + 1]
    }
}


class PhotosController: UIViewController {

    let imageView = UIImageView()

    init(imageUrl: String) {
        if let url = URL(string: imageUrl){
            imageView.sd_setImage(with: url)
        }
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.contentMode = .scaleAspectFill
        view.addSubview(imageView)
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.fillSuperview()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

问题是您正在 viewWillLayoutSubviews() 中设置 swipingPhotosController 的框架 --- 但视图尚未布局。

您需要在 viewDidLayoutSubviews() 中执行此操作(已执行 而不是 ).

但是,viewDidLayoutSubviews() 被调用了很多次,特别是因为您在 scrollViewDidScroll() 中再次更改了框架。

因此,您需要设置一个标志,仅在 viewDidLayoutSubviews() 中设置一次框架(或再次,如果 scrollView 框架已更改)。

不确定您如何或何时设置 SwipingPhotosController 的属性,我这样做是为了测试:

// add a class property
var savedScrollViewWidth: CGFloat = 0.0

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if scrollProfileView.frame.width != savedScrollViewWidth {
        savedScrollViewWidth = scrollProfileView.frame.width
        let imageView = swipingPhotosController.view!
        imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
    }

}

看看这是否能让您的布局正常工作。