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!