Swift UIScrollView - 仅垂直滚动的正确实现

15

提前道歉,我是一个iOS编程的初学者。

我想使用UIScrollView,因为要显示的内容超过了屏幕的高度。 因此,我只想要垂直滚动,而不是水平滚动。

我正在使用Storyboard和AutoLayout来绘制视图。

这是我的UIViewControllerUIScrollView的屏幕截图:

enter image description here enter image description here

然后我将标签更改为大文本,如下所示:

override func viewDidLoad() {
    super.viewDidLoad()

    self.label1Label.text = "Seize jours après la chute du président Blaise Compaoré, le Burkina Faso a un nouveau chef d'EtatA l'issue d'ultimes tractions, civils et militaires se sont accordés, lundi 17 novembre, sur le nom du diplomate Michel KafandoSeize jours après la chute du président Blaise Compaoré, le Burkina Faso a un nouveau chef d'EtatA l'issue d'ultimes tractions, civils et militaires se sont accordés, lundi 17 novembre, sur le nom du diplomate Michel Kafando"
    self.label1Label.numberOfLines = 0
    self.label1Label.sizeToFit()

我的问题是,如果我没有手动为我的contentView(在UIScrollView内部)设置宽度,则滚动会变成水平的,而不是垂直的(请看下面的截图):

enter image description here

我尝试过像我在许多Google帖子中看到的那样设置contentSize,但没有成功:

self.scrollView.contentSize = CGSizeMake(400.0, 600.0)
如果我手动为我的contentview设置宽度(例如:320个点),则滚动将是垂直的(好的),但根据iPhone的尺寸不同,它可能无法覆盖屏幕的整个宽度,如以下截图所示: enter image description here 问题是:如何正确使用UIScrollView来实现只有垂直滚动的contentView,同时遵守自动布局约束(全屏0顶部-0底部-0左侧-0右侧)。
非常感谢您的帮助!
8个回答

42

Mike Woelmer介绍了如何在Atomic Object博客上使用Interface Builder正确实现此操作。

http://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/

我也在github上有自己的代码(没有storyboard实现)。

https://github.com/nadthevlad/AutolayotScrollview

您不需要设置内容或视图的高度或宽度。相反,您应该使用Autolayout的pins和约束来设置scrollview的行为。

  1. 创建一个UIScrollView *scrollView。

  2. 您需要创建一个UIView *contentView,将其余的视图元素放入其中。

[self.view addSubview:scrollView];
[scrollView addSubview:contentView];
[contentView addSubview:_label1];
[contentView addSubview:_label2];
[contentView addSubview:_label3];
[contentView addSubview:_label4];
[contentView addSubview:_label5];
[contentView addSubview:_label6];
  • 将scrollView的4个边缘钉住到self.view的4个边缘。

  • 将contentView的顶部和底部边缘固定在scrollView的顶部和底部。

  • 这是棘手的部分。为了设置水平大小,您希望将contentView的前导(右)和尾随(左)边缘固定在leading和trailing边缘上,而不是scrollView。即使contenView是scrollView的子视图,它的水平约束也会延伸到scrollView之外,并与self.view连接。

  • 像通常一样将任何其他视图元素固定到contentView。


  • 你不必将所有的视图元素都包含在一个视图中,这并非必要。关键是确保约束可以指定可滚动内容的尺寸。来自苹果技术笔记:"滚动视图的子视图上的约束必须产生一个填充大小,然后被解释为滚动视图的内容大小。" 技术笔记链接:https://developer.apple.com/library/ios/technotes/tn2154/_index.html - Daniel
    2
    在第5步中,水平尺寸问题可以通过将ContentView的宽度设置为ScrollView的宽度来解决。这可以在IB中完成。 - Nastya Gorban
    2
    我通过在第4步将contentView的4个边缘设置为scrollView,并在第5步将contentView的宽度设置为self.view的宽度来解决了这个问题。 - netwire
    按照步骤来,一举成功。同时,请记得为内容视图添加固定高度,并将该高度设置为大于设备高度以测试滚动效果。 - Matt Kelly
    除了这个列表之外,我还需要设置内容视图的y值(即垂直居中),并且还必须将尾缘固定到滚动视图上。如果不固定尾缘,它会抱怨滚动模糊不清。 - Aquila Sagitta

    23

    让UIScrollView只能在一个方向上滚动的诀窍是,使UIScrollView在受限维度上的内容大小与同维度下UIScrollView的框架大小相同。因此,在这种情况下,scrollview.contentSize.width 应该等于 scrollview.frame.size.width

    记住这一点,尝试以下操作:

    1. 确保您按照此答案中描述的方式设置了约束条件

    2. 将以下代码添加到您的视图控制器中:

    override func viewWillLayoutSubviews()
    {
        super.viewWillLayoutSubviews();
        self.scrollView.contentSize.height = 3000; // Or whatever you want it to be.
    }
    

    就我个人而言,我真的不是Auto Layout的粉丝。如果你有兴趣在没有Auto Layout的情况下尝试这个——即只用代码而不是约束条件——你可以为视图控制器的视图关闭自动布局,并将viewWillLayoutSubviews方法更改为以下形式:

    override func viewWillLayoutSubviews()
    {
        super.viewWillLayoutSubviews();
    
        self.scrollView.frame = self.view.bounds; // Instead of using auto layout
        self.scrollView.contentSize.height = 3000; // Or whatever you want it to be.
    }
    

    谢谢,经过一些尝试修复,这对我很有帮助! - Ruud Kalis
    诀窍在于在viewWillLayoutSubviews中计算尺寸! - jplozano

    7
    这是我在Storyboard中垂直滚动的常规做法:
    1. 拖拽一个viewController;
    2. 在控制器的主视图中拖拽一个scrollView,并使用约束 L=0, T=0, T=0 和 B=0 将其与其父视图(控制器的主视图)关联;
    3. 在scrollView中拖拽一个UIView,将其作为其内容视图,使用约束L-0, T=0, T=0, B=0 将其与其父视图(scrollView)关联;
    4. 设置scrollView的内容大小,这将控制水平和垂直方向的滚动;
    5. 为了停止水平滚动,请使内容视图的宽度等于scrollView的宽度,并添加相应的约束;
    6. 使内容视图的高度等于控制器视图的高度。这是暂时的,直到我们填充可滚动内容;
    7. 向内容视图添加控件,一直到完成为止;
    8. 最重要的是删除第5步中设置的内容视图高度的约束,改为从内容视图底部的控件创建约束,使其与其底部对齐。这有助于使内容视图的高度从顶部的第一个控件开始,一直延伸到最后一个控件。
    9. 运行程序,如果结果不符合预期,请让我知道。
    希望这能够帮到您!

    非常感谢,它完美地工作了!在开发过程中,您可以直接跳过第5步(滚动和控件无法工作,但您可以构建应用程序)。 - user3856297

    2
    你可以正常定义ScrollView并正常约束ScrollView的属性。最初的回答。
            let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.backgroundColor = .white
        self.view.addSubview(scrollView)
    
        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true
    

    "最初的回答":但是诀窍在于如何定义标签(或其他视图),使其宽度可能比屏幕更宽。 不使用

    myLabel.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -10.0).isActive = true
    

    你应该使用 "最初的回答"。
    myLabel.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
    

    这个第二行代码可以防止视图水平滚动。对所有子视图都这样做,就不会发生水平滚动(仍会按计划垂直滚动)。

    另外,别忘了设置最后一个子视图的底部约束,以确保您的UIScrollView可以垂直滚动。

    注:Original Answer翻译成"最初的回答"

    labelX.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -10.0).isActive = true
    

    0

    我迟到了.. 如果你将标签的宽度约束设置为视图相等,那么你可以实现相同的效果。

     aboutCaption.widthAnchor.constraint(equalTo: self.view.widthAnchor , multiplier: 0.9).isActive = true
    

    这是我的示例代码

    class AboutVC: UIViewController{
    
    let scrollView = UIScrollView()
    
    lazy var aboutLbl : UILabel = {
        let lbl = UILabel()
        lbl.text = "About"
        lbl.font = .boldSystemFont(ofSize: 19)
        return lbl
    }()
    
    lazy var aboutCaption : UILabel = {
        let lbl = UILabel()
        lbl.text = "Hi. My name is Uzumaki Naruto. I am a professional MMA Coach with 15+ years of experience. I specialize in ninjutsu, shadow clones and various other techniques. I am in the TOP50 coaches in the Sports category. Been with GIGIMOT for 5 years already. Not a single bad review. "
        lbl.numberOfLines = 0
        lbl.font  = UIFont(name: "Helvetica Neue Thin", size: 16)!
        lbl.textColor = .systemGray2
        return lbl
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        //        tableView.dataSource = self
        //        tableView.delegate = self
    }
    
    func configureUI() {
        self.view.backgroundColor = UIColor.white
        
        //Add and setup scroll view
        self.view.addSubview(self.scrollView)
        scrollView.backgroundColor = .white
        self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
        self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true;
        self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true;
        self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true;
        self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true;
        
        scrollView.addSubview(aboutLbl)
        
        aboutLbl.anchor(top: scrollView.topAnchor, left: scrollView.leftAnchor , paddingTop: 10 , paddingLeft: 10)
        
        
        scrollView.addSubview(aboutCaption)
        aboutCaption.anchor(top: aboutLbl.bottomAnchor, left: scrollView.leftAnchor, bottom: scrollView.bottomAnchor , right: scrollView.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, paddingRight: 0)
        aboutCaption.widthAnchor.constraint(equalTo: self.view.widthAnchor , multiplier: 0.9).isActive = true
        
    }}
    

    0

    感谢@NadtheVlad,我终于看到了光明。进一步的摆弄教会了我,contentView只需要固定在scrollView的顶部、底部和宽度即可,无需引用self.view。

    使用EasyPeasy,这很简单:

    scrollView <- Edges()
    contentView <- [Top(), Bottom(), Width().like(scrollView)]
    

    0
    2023年的确切答案是:
    垂直示例:
    var scroll: UIScrollView
    var tall: UIView // some tall view, imagine a few screen heights.
    
    1. 使用顶部/右侧/底部/左侧约束将 scroll 的大小固定到其父视图。

    2. tall 设为 scroll 的子视图。

    3. 信不信由你,对于 tall,将 底部、顶部和宽度 设置为与其父视图(即 scroll)相等。

    就是这样。


    不要忘记,显然地设置 tall 的高度。(例如,如果有 10 个标签,请显然地将它们连接起来,以便这些标签设置 tall 的高度,或者无论您在 tall 中做什么。当然,不要忘记设置 translatesAutoresizingMaskIntoConstraints = false

    -1

    我只在水平/垂直滚动视图中使用此实现,虽然它是用Objective-C编写的,但如果您有任何不清楚的地方,我会尝试解释其逻辑。

    //Getting iDevice's screen width
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    CGFloat screenWidth = screenRect.size.width;
    
    //Setting the right content size - only height is being calculated depenging on content.
    CGSize contentSize = CGSizeMake(screenWidth, HEIGHT_OF_YOUR_CONTENT);
    self.scrollView.contentSize = contentSize;
    

    现在,scrollview 的 contentSize 属性的宽度被绑定到设备屏幕的实际宽度上,因此 scrollview 只会在垂直方向上滚动。


    很遗憾,我尝试了你的解决方案,但是contentSize似乎没有影响UIScrollView。你知道为什么吗? - fabdarice
    据我所知,这是一个教程代码,请问您能否在这里上传项目? - Richard Topchii

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接