简体   繁体   中英

What are the necessary steps to create a UIScrollView programmatically as a subview of another UIView?

TL;DR -> what are the necessary steps to create a class that extends UIScrollView, instantiate that class, and then add it as a subview of an existing UIView using NSLayoutConstraints for my UI?

I'm in a situation where I need to add a UIScrollView to an existing UIViewController's subview (not self.view - a child of self.view). I'm also not using IB because I want to understand my code. I've managed to lay out each UI component, and I can see that my view hierarchy has been assembled correctly in the debugger.

Unfortunately, my UIScrollView will not scroll and for some reason none of my subviews are being displayed visually (note that the immediate children of my scroll view are also scroll views so my problem is likely my understanding of the UIScrollView class).

在此处输入图片说明

The above image shows the general structure of my UI, and the light gray area is my UIScrollView. I have a class that extends UIScrollView and UIScrollDelegate (not 100% if the latter parent class is necessary). The class looks like this

import UIKit

class PlayWorkout: UIScrollView, UIScrollViewDelegate{

var workoutInstructionTiles : [WorkoutInstruction] = [WorkoutInstruction]();

var heightsTotal : Int = 0; //This variable will keep track of the running total for each of the workout tile subviews to help with positioning

init(workoutSubviews : [WorkoutInstruction]){

    super.init(frame : CGRect.zero);
    workoutInstructionTiles = workoutSubviews;
    appendTiles();

    self.translatesAutoresizingMaskIntoConstraints = false;

}


func appendTiles(){

    print("appending tiles \(workoutInstructionTiles.count)");
    for(var i = 0; i < workoutInstructionTiles.count; i++){

        let setCount = workoutInstructionTiles[i].getNumSets();

        workoutInstructionTiles[i].layer.cornerRadius = 5;
        workoutInstructionTiles[i].backgroundColor    = UIColor.whiteColor();
        workoutInstructionTiles[i].translatesAutoresizingMaskIntoConstraints = false;
        changeContentSize(workoutInstructionTiles[i]);
        self.addSubview(workoutInstructionTiles[i]);

        //Formula for position relative to top of the ScrollView will be 10 (margin) + 20 (label space) + setCount * 20 (content) + heightsTotal
        let verticalConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: CGFloat(30  + heightsTotal));

        let leftConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 10);

        let rightConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 10);

        let heightConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: CGFloat(setCount * 20 + 20));

        self.addConstraints([verticalConstraint, leftConstraint, rightConstraint, heightConstraint]);
        heightsTotal += setCount * 20;
    }

}



func setViewConstraints(superView : UIView) -> Void{

    let verticalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant:30);

    let horizontalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.CenterXWithinMargins, multiplier: 1.0, constant: 0);

    let heightConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: -60);

    let widthConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0);

    superView.addConstraints([verticalConstraint, horizontalConstraint, heightConstraint, widthConstraint]);

}

func changeContentSize(aView : UIView){

    var contentRect : CGRect = CGRect.zero;
    for view in aView.subviews {
        contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.contentSize = contentRect.size;
}



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

I then implement the class when a button is clicked to start a workout. From what I've been able to ascertain thus far it seems like the only methods I need to call/set for UIScrollView are

scrollView.contentSize = CGSize(width: aWidth, height: aHeight)
scrollView.translateAutoResizingMaskIntoConstraints = false

So, here's my class instantiation - this is inside of a UIGestureRecognizer event handler inside of a UIViewController that also extends UIScrollViewDelegate:

workoutConfig = PlayWorkout(workoutSubviews : workoutInstructions);
workoutConfig.delegate = self;
workoutConfig.changeContentSize(workoutConfig);
workoutConfig.translatesAutoresizingMaskIntoConstraints = false;
self.contentView.addSubview(workoutConfig);
workoutConfig.setViewConstraints(self.contentView);

I'm also using a method called changeContentSize() that was adapted from an objective-C method posted to SO that looks like this:

func changeContentSize(aView : UIView){

    var contentRect : CGRect = CGRect.zero;
    for view in aView.subviews {
        contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.contentSize = contentRect.size;
}

I know that's a lot of reading, but I wanted to be thorough. I appreciate any help. Thanks!

Ok, so after 3 days of maddening research and going down all kinds of nonsense paths to accomplish this I FINALLY get it, and it's actually not that hard (just extremely poorly documented by apple). I'm going to try to share what I found - this link is what finally helped me understand how to do this so I feel obliged to give credit where it's due:

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

Ok, here's the step by step to create a scrollable view using auto layout.

1 - a UIScrollView can only have 1 child view, which should generally be a UIView to hold all of the scrollable content. Make sure to create a UIView, add any content you want to scroll to that UIView, and then add that UIView as a subview of your UIScrollView

2 - Pin the scroll view to its superview with whatever constraints you want, just make sure you set the top, leading, trailing and bottom. Here's what my code looked like (self is a class extending UIScrollView in my case):

func setViewConstraints(superView : UIView) -> Void{

    let verticalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant:60);

    let leftConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0);

    let rightConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0);

    let bottomConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Bottom, relatedBy: .Equal, toItem: superView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0);


    superView.addConstraints([verticalConstraint, leftConstraint, bottomConstraint,rightConstraint]);

}

3 - Pin that child UIView mentioned in step 1 to the scroll view ensuring to set -> top, leading, trailing, and bottom. This should generally be pinned exactly unless the UIView is designed to be smaller than the dimension of the scroll view. This step is accomplished to ensure that the device knows where to place the UIView. My code looked like this (again, self is referring to a class that extends UIScrollView)

    let pinTop = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant:0);

    let pinBottom = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0);

    let pinLeft = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0);

    let pinRight = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0);


    self.addConstraints([pinTop, pinBottom, pinLeft, pinRight]);

4 - in order for the scroll view to understand the content size of the view, you MUST SET THE HEIGHT AND THE WIDTH OF THE UIVIEW BASED ON THE SCROLL VIEWS SUPERVIEW OR A CALCULATION THAT DOESN'T INVOLVE THE SCROLL VIEW. Don't worry about setting other dimensions of the UIView relative to the scroll views superview, just height and width are necessary for the cocoa api to infer the content size. My code looked like this (self.viewController.contentView in this case refers to the scroll view's superview):

    let widthConstraint = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self.viewController.contentView, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0);


    let heightConstraint = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: CGFloat(heightsTotal + (self.workoutInstructionTiles.count * 10)));


    self.viewController.contentView.addConstraints([heightConstraint, widthConstraint]);

If each of these steps are followed correctly, your result should be a scrollable view. Now that I get this if I can help anyone with their code just leave a comment.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM