Senin, 05 Januari 2015

Auto Layout and UIScrollView: A “Pure Auto Layout Approach” iOS Tutorial in Swift

With the advent of Auto Layout to iOS, more and more of the applications that we write will incorporate this new technology. If you are not using Auto Layout, then you are most likely writing error prone code that does a lot of mathematics that adjust your views for various situations. Auto layout is math but without all the complexities of doing mathematics. It simplifies the mathmatical process by abstracting out the most arduous part of computing view geometries—namely, view geometry dependencies. Solving simultaneous equations is a thing of the past with Auto Layout. We now “simply” specify the relational attributes between view geometric properties and let Auto Layout do the rest! However, as we all know, the introduction of a new framework brings a new set of hurdles to the table. One of the major hurdles that we have encountered is learning how to speak Auto Layout. We need to be very precise when using this new API. Our most recent difficult encounter was learning how to apply Auto Layout to a UIScrollView so that it behaves in a predictable way.

Introduction
In this tutorial, using a “pure Auto Layout approach,” we will programmatically create a UIScrollView with three subviews of UIView's. (See video below)


These three subviews, colored blue, yellow and green, will comprise the scrollable content area of our scroll view. The size and position of our scroll view will be animated into place by modifying its Auto Layout constraint values. The size of our scroll view (the view port) will be defined as a ratio of the width and height of the device's screen. The width of the scrollable area will be defined as one and a half times that of the scroll view's width. Our scroll view along with its subviews will dynamically resize to support both landscape and portrait view orientations, however, the ratio of the scrollable area to that of the scroll view's width will not change. If you are not familiar or in need of a refresher of these two technologies follow these links: UIScrollView, Auto Layout.

Setting Up
Download and boot-up the latest version of Xcode. From the Welcome screen, press (command+shift+1) if it's not visible, select the “Create a new Xcode project” from the option menu.

Welcome Screen

On the next menu, select "Single View Application" and click on the button labeled "Next."

Select Type of Application

For the following menu fill out all the fields as shown. For the "Organization Name" field you may type in the name of your organization instead.

Project Options

On the final menu for this setup you are prompted to select a location where this project will live. You are also given the option to place this project under source control using git. If you know how to use git then by all means use it! Otherwise uncheck the selection box where it reads "Source Control."

Code Structure
From the "Navigator" window, typically positioned to the left of Xcode, select the "Project Navigator" icon located in the top left corner.

Navigator

(If you can't see the navigator window as shown in the figure above then you may need to unhide it. To do this, in the top right corner of Xcode you will find a button with three segments, select the left most segment to show or hide the "Navigator" window). Double click on the yellow folder labeled Auto Layout and Scroll View to reveal its contents. Select the file named ViewController.swift. In the text editor you will find the boilerplate source code that defines the subclass of this view controller. Replace all the code for the class definition with the following:

class ViewController: UIViewController {
// Set scroll view height to 35% of root view height //
let scrollViewHeightAsPercentageOfRootView : CGFloat = 0.36
// Set scroll view width to 85% of root view width //
let scrollViewWidthAsPercentageOfRootView : CGFloat = 0.85
let asvScrollView : UIScrollView = UIScrollView(frame: CGRectZero)
let asvContainerView : UIView = UIView(frame: CGRectZero)
let asvLeftView : UIView = UIView(frame: CGRectZero)
let asvMiddleView : UIView = UIView(frame: CGRectZero)
let asvRightView : UIView = UIView(frame: CGRectZero)
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// code placeholder tag //
}
/**
Installs subviews inside a scroll view container that defines the contents
and resizes proportionatly to the root view by leveraging autolayout.
*/
func asvInstallSubviewsInsideTheScrollViewContainerWithConstraints() {
// code placeholder tag //
}
/**
Installs a scroll view without constraints. We delay the installation of the
constraints so that we can animate them into place later on in the code.
*/
func asvInstallScrollViewWithoutConstraints() {
// code placeholder tag //
}
/**
Installs a scroll view container that resizes proportionatly to the root view
by leveraging autolayout.
*/
func asvInstallScrollViewContainerWithConstraints() {
// code placeholder tag //
}
/**
Installs height and width constraints on our scroll view
*/
func asvApplyHeightAndWidthConstraints() {
// code placeholder tag //
}
/**
Installs centering constraints on our scroll view
*/
func asvApplyCenteringConstraints() {
// code placeholder tag //
}
/**
Installs label with blog name
*/
func asvInstallBlogName() {
// code placeholder tag //
}
}
view raw gistfile1.swift hosted with ❤ by GitHub
The code above is the entire skeletal code of our project. Code lines 4 and 5 define the height and width of our scroll view as a ratio of its the root view. The root view is the top most parent view in our view hierarchy; it fills the entire devices screen.

View Hierarchy
From the code snippet above, code lines 8 through 12 are the view objects that will comprise our project's complete view hierarchy. The asvContainerView object is a subview, or child view, of the asvScrollView object. The asvLeftView, asvMiddleView and asvRightView are sibling objects and subviews of asvContainerView. The sibling views are the objects that make up the visible contents of the scroll view.

Figure 5. View Hierarchy

Figure 5 is a visual representation of the described view hierarchy. The brown colored view is the asvScrollView object; the parent object of the views that sit in front of it. The cyan colored view is the asvContainerView object. The blue, yellow and green are the asvLeftView, asvMiddleView and asvRightView, respectively.

Meat and Potatoes
(Optioanal) The first function, asvInstallBlogName, that we define sets up a label for our blog's name. In this function, find the comment "// code placeholder tag //" and replace it with the following lines of code:

let blogLabel : UILabel = UILabel()
let topLC : NSLayoutConstraint = NSLayoutConstraint(
item: blogLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier:1.0, constant: 64)
let leftLC : NSLayoutConstraint = NSLayoutConstraint(
item: blogLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
let rightLC : NSLayoutConstraint = NSLayoutConstraint(
item: blogLabel, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0)
blogLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(blogLabel)
self.view.addConstraints([topLC,leftLC,rightLC])
blogLabel.textAlignment = NSTextAlignment.Center
blogLabel.textColor = UIColor.darkGrayColor()
blogLabel.text = "aGupieWare"
blogLabel.font = UIFont(name: "Helvetica-Bold", size: 26)
view raw gistfile1.swift hosted with ❤ by GitHub


For the function asvApplyCenteringConstraints locate the comment "// code placeholder tag //" and replace it with the code below:

let centerXLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvScrollView, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.0, constant: 0)
let centerYLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvScrollView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0)
self.view.addConstraints([centerXLC,centerYLC])
view raw gistfile1.swift hosted with ❤ by GitHub
This function simply centers our scroll view, asvScrollView, object relative to it's root parent view.


Locate the function asvApplyHeightAndWidthConstraints and replace the comment "// code placeholder tag //" with the following lines of code:

let heightLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvScrollView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.Height, multiplier: self.scrollViewHeightAsPercentageOfRootView, constant: 0)
let widthLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvScrollView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal,
toItem: self.view, attribute: NSLayoutAttribute.Width, multiplier: self.scrollViewWidthAsPercentageOfRootView, constant: 0)
self.view.addConstraints([heightLC,widthLC])
view raw gistfile1.swift hosted with ❤ by GitHub
In this function we simply install the height and width constraints on our scroll view, asvScrollView, object. As mentioned earlier, the height and width of this scroll view is a fixed ratio to that of its parent root view—the devices visible screen area.


Inside the function asvInstallScrollViewContainerWithConstraints locate the comment "// code placeholder tag //" and replace with the following code:

// Prevent system from creating layout constraints //
self.asvContainerView.setTranslatesAutoresizingMaskIntoConstraints(false)
// The width of the scrollable area is defined as one //
// and a half times that of the scroll view's width //
let widthRatio : CGFloat = 1.5;
// Add the container view and set its constraints so that //
// it resizes with the same proportions on screen rotations //
asvScrollView.addSubview(asvContainerView);
let topLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let leadingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
let trailingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0)
let bottomLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0)
let widthLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Width, multiplier: widthRatio, constant: 0)
let heightLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvContainerView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvScrollView, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0)
self.asvScrollView.addConstraints([topLC,leadingLC,trailingLC,bottomLC,widthLC,heightLC])
self.asvContainerView.backgroundColor = UIColor.cyanColor()
view raw gistfile1.swift hosted with ❤ by GitHub
This function adds a scroll view container, our asvContainerView, as a subview of the root view along with Auto layout constraints so that it resizes proportionately to the various root view sizes.


For the function asvInstallScrollViewWithoutConstraints replace the comment "// code placeholder tag //" with the following lines of code:

// Set scroll view properteis //
self.asvScrollView.backgroundColor = UIColor.brownColor()
self.asvScrollView.bounces = false
// Prevent the system from automatically assinging auto layout //
// constraints when adding our scroll view to the view hierarchy //
self.asvScrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(self.asvScrollView)
view raw gistfile1.swift hosted with ❤ by GitHub
In this function we install asvScrollView object as a subview of the root view, without constraints. We delay the installation of the constraints so that we can animate their installation into place later on in this tutorial.


Inside the function viewDidAppear replace the comment "// code placeholder tag //" with the following lines of code:

// Setup blog name //
self.asvInstallBlogName()
// Setup the scroll view //
self.asvInstallScrollViewWithoutConstraints()
// Setup the scroll view container //
self.asvInstallScrollViewContainerWithConstraints()
// Setup the scroll view container content views //
self.asvInstallSubviewsInsideTheScrollViewContainerWithConstraints()
// apply custom constraints to our scroll view //
self.asvApplyHeightAndWidthConstraints()
self.asvApplyCenteringConstraints()
view raw gistfile1.swift hosted with ❤ by GitHub
The first four lines of code sets up our view hierarchy by calling methods that we have stubbed out; we will later define these methods. The last two lines of code call our, stubbed out, custom methods that setup the height, width and centering constraints of our asvScrollView object.


Replace the comment "// code placeholder tag //" inside the function asvInstallSubviewsInsideTheScrollViewContainerWithConstraints with the following lines of code:

// Prevent system from creating layout constraints //
self.asvLeftView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.asvMiddleView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.asvRightView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.asvContainerView.addSubview(self.asvLeftView)
self.asvContainerView.addSubview(self.asvMiddleView)
self.asvContainerView.addSubview(self.asvRightView)
// Constrain the left subview to the left, top and bottom of its container view //
let leftTopLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvLeftView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let leftBottomLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvLeftView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0)
let leftLeadingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvLeftView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
// Constrain the left subview's right side to the middle subview's //
// left side so that the two views are touching each other //
let leftTrailingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvLeftView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvMiddleView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
// Constrain the middle subview to the top and bottom of its container view //
let middleTopLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvMiddleView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let middleBottomLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvMiddleView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0)
// Constrain the middle subview's right side to the right subview's //
// left side so that the two views are touching each other //
let middleTrailingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvMiddleView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvRightView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
// Constrain the right subview to the right, top and bottom of its container view //
let rightTopLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvRightView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let rightBottomLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvRightView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0)
let rightTrailingLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvRightView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvContainerView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0)
// add the above constraints to the container view -- the //
// superview of the left, middle and right subviews //
self.asvContainerView.addConstraints([leftTopLC,leftBottomLC,leftLeadingLC,leftTrailingLC,
middleTopLC,middleBottomLC,middleTrailingLC, rightTopLC,rightBottomLC,rightTrailingLC])
// Constrain the left, middle and right subviews so that they all have equal width. //
// we set the 'multiplier' to 0.99,-slightly widder middle view,- to eliminate a //
// a vertical line that appears to the left of the middle view. //
let leftMiddleWidthLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvLeftView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvMiddleView, attribute: NSLayoutAttribute.Width, multiplier: 0.99, constant: 0)
let rightMiddleWidthLC : NSLayoutConstraint = NSLayoutConstraint(
item: self.asvRightView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal,
toItem: self.asvMiddleView, attribute: NSLayoutAttribute.Width, multiplier: 0.99, constant: 0)
// Add the width constraints from above //
self.asvContainerView.addConstraints([leftMiddleWidthLC,rightMiddleWidthLC])
self.asvLeftView.backgroundColor = UIColor.blueColor()
self.asvMiddleView.backgroundColor = UIColor.yellowColor()
self.asvRightView.backgroundColor = UIColor.greenColor()
view raw gistfile1.swift hosted with ❤ by GitHub
The above code installs subviews on our asvContainerView object. As mentioned earlier, these subviews comprise the visible content of our scroll view. We also setup Auto Layout constraints so that these subviews resize proportionately to their container view; a desired behavior when supporting various device screen sizes and device orientations—landscape and portrait modes. We also setup differing colors on these subviews to be able to readily discern between them.

Liftoff
Compile and run the application in any of the iPhone simulators. Once your application is running you should see on your simulators screen something similar to the image depicted in figure 6, while your simulator is in portrait mode.

Figure 6

After rotating you simulator to landscape mode you should see something similar to figure 7. Try to scroll the colored images from left to right.
Figure 7


Brief Animation
For our finishing touches we add a little bit of animation. Locate the following lines of code inside the viewDidAppear function,

// apply custom constraints to our scroll view //
self.asvApplyHeightAndWidthConstraints()
self.asvApplyCenteringConstraints()
view raw gistfile1.swift hosted with ❤ by GitHub
and replace it with the following code:

// -- animate the placements of our auto layout constraints -- //
// Place the scroll view slightly offset from center before animating //
self.view.layoutIfNeeded()
self.asvScrollView.center = CGPointMake(CGRectGetMidX(self.view.frame), CGRectGetHeight(self.view.frame)/8.0)
UIView.animateWithDuration(1.75, animations: { () -> Void in
// apply custom constraints to our scroll view //
self.asvApplyHeightAndWidthConstraints()
self.asvApplyCenteringConstraints()
self.view.layoutIfNeeded()
}) { (animated) -> Void in
// -- animation completion block -- //
// Once the initial animation is complete we animate the //
// contents of the scroll view by changing its content offset. //
self.asvScrollView.setContentOffset(CGPointMake(CGRectGetMaxX(self.asvLeftView.frame), 0), animated: true)
}
view raw gistfile1.swift hosted with ❤ by GitHub

Source:http://blog.agupieware.com/2014/12/auto-layout-and-uiscrollview-pure-auto.html

Tidak ada komentar:

Posting Komentar