即使设置了 CGRect,UIScrollView 中的 UIPageViewController 仍会显示为全屏 - 用户滚动后立即返回正确的帧值

Posted

技术标签:

【中文标题】即使设置了 CGRect,UIScrollView 中的 UIPageViewController 仍会显示为全屏 - 用户滚动后立即返回正确的帧值【英文标题】:UIPageViewController within a UIScrollView appears fullscreen even though CGRect is set - returns to correct frame values as soon as user scrolls 【发布时间】:2020-03-06 14:11:36 【问题描述】:

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

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

这是我在模拟器中按下运行时得到的结果 - 一个全屏的页面浏览控制器:

只要稍微滚动一下,这里的视图就会恢复正常

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

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: $0 == 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: $0 == 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")
    

【问题讨论】:

您能否也包括您的SwipingPhotosController 的来源?如果没有它,很难尝试看看会发生什么...... @DonMag 现在肯定会这样做 @DonMag 你好...我已按要求添加了控制器代码 好的 - 你在哪里设置 user SwipingPhotosController @DonMag 实际上忘记在配置文件控制器中添加它。认为这与当前的错误无关,因此发出了该代码。因此,当配置文件控制器初始化 SwipingPhotosController 时,我还将用户对象从配置文件传递给 swipingphotos。虽然该错误与 UI 相关,但并不认为这很重要。 【参考方案1】:

问题是您在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)
    


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

【讨论】:

非常感谢会尝试一下。再澄清一下,我是否还应该在 viewDidLayoutSubviews 中添加我想要添加的其他子视图?或者该代码是否可以保留在视图中并加载。在这种情况下我说的是nameLabel @Archid - 一般来说,不要在viewDidLayoutSubviews() 中添加子视图。将它们添加到viewDidLoad() ...同时在viewDidLoad() 中设置约束。唯一需要使用viewDidLayoutSubviews() 的时候是在设置框架值时,这些值依赖于其他视图已经就位。有了你正在做的事情,你甚至可能会更好地设置swipingPhotosController.view 的约束,然后更新.constantscrollViewDidScroll() 中的高度值 是的,这行得通。非常感谢。不过仍然需要一些测试,如果有任何与此相关的 UI 错误,我会更新这篇文章。但我想是的,问题已经解决了

以上是关于即使设置了 CGRect,UIScrollView 中的 UIPageViewController 仍会显示为全屏 - 用户滚动后立即返回正确的帧值的主要内容,如果未能解决你的问题,请参考以下文章

滚动后如何获取 UIScrollView 的 CGRect?

将 UIScrollView 委托设置为它自己的自定义类

UIScrollView - 无法向下滚动很远(即使我已将 contentsize 设置为 1000)[重复]

在 for 循环中创建 CGRect 并将其分配给 UIView

为啥当我在 uiscrollview 顶部添加对象时,即使我将所有四个约束都设置为 0,对象也会下降一点?

设置了 contentsize 的 UIScrollView 无法滚动超出帧边界