#nslayoutconstraint
Explore tagged Tumblr posts
Video
youtube
Swift: Create Facebook (Part 2) - UICollectionView, NSLayoutConstraint
my review point is 8/10
https://youtu.be/ZwBYQpLQAvw?t=8m15s button style ( setTitle, setTitleColor , setImage , titleEdgeInsets)
https://youtu.be/ZwBYQpLQAvw?t=14m35s equally spaced out elements
https://youtu.be/ZwBYQpLQAvw?t=18m37s refresh collection view when user change orientation ( viewWillTransitionToSize , invalidateLayout)
#ios#brian#facebook feed#facebook#setTitle#setTitleColor#button#setImage#titleEdgeInsets#insets#inset#equal#equally#viewWillTransitionToSize#orientation#invalidateLayout#NSLayoutConstraint#UICollectionView
0 notes
Photo
New Post has been published on https://fitnesshealthyoga.com/refactoring-with-uistackview/
Refactoring with UIStackView
Written by Nicholas Steppan Meschke, Software Engineer iOS
What awaits you
As developers who care about the user experience of our app, we’ll likely encounter some scenarios that require a view to reflect different states. Sometimes, the state of the view may even impact its size. The code that handles all those transitions can quickly become a mess. In this article, we want to share our approach to implementing such scenarios using UIStackView.
Let’s start with an example
We use a simple app idea to demonstrate how we can achieve that. We already have a demo app that has a simple button, which increases a counter when tapped. The number of taps is displayed on the screen. To make the demo app more interesting, we’ll add functionality to set and track tapping goals!
We can now set goals! Here’s the code that’s responsible for setting up the constraints for the empty tapping goal view.
private func setupEmptyGoal() // … code left out NSLayoutConstraint .activate([emptyGoalTitleLabel.topAnchor.constraint(equalTo: emptyGoalView.topAnchor, constant: 16), emptyGoalTitleLabel.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16), emptyGoalTitleLabel.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16)]) NSLayoutConstraint .activate([optionsStackView.topAnchor.constraint(equalTo: emptyGoalTitleLabel.bottomAnchor, constant: 32), optionsStackView.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16), optionsStackView.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16), optionsStackView.bottomAnchor.constraint(equalTo: emptyGoalView.bottomAnchor, constant: -16)]) NSLayoutConstraint .activate([emptyGoalView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), emptyGoalView.bottomAnchor.constraint(equalTo: bottomAnchor), emptyGoalView.trailingAnchor.constraint(equalTo: trailingAnchor), emptyGoalView.leadingAnchor.constraint(equalTo: leadingAnchor)])
The code sets the constraints for the view in the EmptyGoal state. Cool, we now have the state where we don’t have a goal, so let’s create one for when a goal is set.
Here’s the code responsible for creating and setting up view for when we have a goal set. Let’s call this state GoalSet.
private func setupGoalView() // … code left out NSLayoutConstraint .activate([goalTitleLabel.topAnchor.constraint(equalTo: goalView.topAnchor, constant: 16), goalTitleLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor)]) NSLayoutConstraint .activate([progressLabel.topAnchor.constraint(equalTo: goalTitleLabel.bottomAnchor, constant: 16), progressLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor), progressLabel.bottomAnchor.constraint(equalTo: goalView.bottomAnchor, constant: -16)]) NSLayoutConstraint .activate([goalView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), goalView.topAnchor.constraint(equalTo: goalSetLabel.bottomAnchor, constant: 16), goalView.bottomAnchor.constraint(equalTo: bottomAnchor), goalView.trailingAnchor.constraint(equalTo: trailingAnchor), goalView.leadingAnchor.constraint(equalTo: leadingAnchor)])
Awesome, now we can set a tapping goal and track it in our app. However, there’s one issue here: even though the spacing in our view in the GoalSet state should be 16pt, it appears to be a lot more. That’s weird, but let’s keep improving our app by introducing a Success state, for when we achieve our goal!
Here’s our Success state view and below, the code for it:
private func setupSuccessView() // … code left out NSLayoutConstraint .activate([imageView.topAnchor.constraint(equalTo: successView.topAnchor), imageView.bottomAnchor.constraint(equalTo: successView.bottomAnchor), imageView.trailingAnchor.constraint(equalTo: successView.trailingAnchor), imageView.leadingAnchor.constraint(equalTo: successView.leadingAnchor)]) NSLayoutConstraint .activate([successView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), successView.topAnchor.constraint(equalTo: goalSetLabel.bottomAnchor, constant: 16), successView.bottomAnchor.constraint(equalTo: bottomAnchor), successView.trailingAnchor.constraint(equalTo: trailingAnchor), successView.leadingAnchor.constraint(equalTo: leadingAnchor)])
But what’s wrong?
Yay, now we have our 3 state views for our clicking goal app! But when we open our app, it looks like this:
Apart from looking really strange and stretched, we have a bunch of constraints that basically do the same thing: constrain the state views to our superView. How can we improve that?
UIStackView to the rescue!
UIStackViews are a really useful tool for laying out a collection of views in either column or row. In our case, we are going to use the UIStackView to group our state views, and it will adapt its size according to what is inside it. Our new code looks like this:
private func setupStackView() // … code left out NSLayoutConstraint .activate([stackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), stackView.trailingAnchor.constraint(equalTo: trailingAnchor), stackView.leadingAnchor.constraint(equalTo: leadingAnchor), stackView.bottomAnchor.constraint(equalTo: bottomAnchor)]) private func setupSuccessView() // … code left out NSLayoutConstraint .activate([imageView.topAnchor.constraint(equalTo: successView.topAnchor), imageView.bottomAnchor.constraint(equalTo: successView.bottomAnchor), imageView.trailingAnchor.constraint(equalTo: successView.trailingAnchor), imageView.leadingAnchor.constraint(equalTo: successView.leadingAnchor)]) private func setupGoalView() // … code left out NSLayoutConstraint .activate([goalTitleLabel.topAnchor.constraint(equalTo: goalView.topAnchor, constant: 16), goalTitleLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor)]) NSLayoutConstraint .activate([progressLabel.topAnchor.constraint(equalTo: goalTitleLabel.bottomAnchor, constant: 16), progressLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor), progressLabel.bottomAnchor.constraint(equalTo: goalView.bottomAnchor, constant: -16)]) private func setupEmptyGoal() // … code left out NSLayoutConstraint .activate([emptyGoalTitleLabel.topAnchor.constraint(equalTo: emptyGoalView.topAnchor, constant: 16), emptyGoalTitleLabel.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16), emptyGoalTitleLabel.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16)]) NSLayoutConstraint .activate([optionsStackView.topAnchor.constraint(equalTo: emptyGoalTitleLabel.bottomAnchor, constant: 32), optionsStackView.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16), optionsStackView.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16), optionsStackView.bottomAnchor.constraint(equalTo: emptyGoalView.bottomAnchor, constant: -16)])
The layouts are now being shown according to their respective constraints, and our code looks simpler and easier to understand. By using the UIStackView, we swapped 3 addSubview calls for 1, and 12 layout constraints for only 4.
Wait, that’s not all!
Right now, we manage our states through 3 methods and to set our view state to the desired state, we need to call all of them, which looks something like this:
showGoal(false) showEmptyGoal(false) showSuccessView(true)
That’s not optimal for maintainability, and understanding the code could obviously be easier. To fix this, we can introduce a new Enum with values that represent our states. Our code should look like this:
enum GoalViewState case empty, goal(value: GoalSize), success enum GoalSize: Int case small = 15, medium = 45, big = 100 var text: String return String(self.rawValue)
Inside our view, we create a new property “state” of type GoalViewState. It’s responsible for storing and handling our state changes using didSet method. The resulting code looks like this:
var state: GoalViewState = .empty didSet updateState(with: state) private func updateState(with state: GoalViewState) stackView.arrangedSubviews.forEach $0.isHidden = true goalSetLabel.isHidden = true switch state case .empty: emptyGoalView.isHidden = false case .goal(let value): goalView.isHidden = false goalSetLabel.isHidden = false setGoal(value) case .success: successView.isHidden = false
Ok, but what does it do and how does it work? Simple! If we need to change the state of our view, we can call “state = .goal(.small)”, making the code more readable and less error-prone, giving whoever reads it a better idea of what’s going on.
Wrapping up
UIStackViews are a quite powerful tool, but like other tools, they are not a silver bullet for every problem. Our constraint problem could have been fixed using a height constraint in the superview, however, that would require us to manually calculate the view size, which would make maintenance more difficult later on. Using a UIStackView, the size is inferred using its contents. This not only gives us a convenient way to have our views correctly sized, but also is robust enough to deal with other requirements, such as showing two states at once. That’s it for today!
Have you ever used a UIStackView as we described in this article? Let us know in the comment section below!
***
//check Cookie Opt out and User consent if(!getCookie("tp-opt-out")) !function(f,b,e,v,n,t,s)if(f.fbq)return;n=f.fbq=function()n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments);if(!f._fbq)f._fbq=n; n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)(window, document,'script','https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1594940627485550'); // Insert your pixel ID here. fbq('track', 'PageView');
Source link
0 notes
Text
Layout constraints in Swift
Layout constraints in Swift
Hi Friends,
AutoLayout constraints are easy now.
Bellow extension class was helpful to set a constraints. Check out below
import UIKit struct AnchoredConstraints { var top, leading, bottom, trailing, width, height: NSLayoutConstraint? }
extension UIView { @discardableResult func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing:…
View On WordPress
#addConstraints#APP#Apple#Constraint#Developer#Developers#Development#forum#how to make an ios app#Ios#iPad#iPhone#iPod#kathir#ktr#ktr kathir#ktrakathir#ktrkathir#Layout#MAC#mobile Developer#Sample code#Samplecode#Source code#Swift $.2#Swift 4#Tutorial#Tutorials#wordpress
0 notes
Text
Animate View Auto Layout iOS Tutorial
With the introduction of the Adaptive Layout all views use Auto Layout by default. However, when animating views the constraints applied to a view prevents the ability to move this view using animation. In this tutorial the Auto Layout constraints will be updated to enable the view movement. This tutorial is made with Xcode 10 and built for iOS 12.
Open Xcode and create a new Single View App.
For product name, use IOS12AnimateViewsTutorial and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and choose Next.
Go to the Storyboard. Drag a View from the Object Library to the main View and in the Attributes Inspector give it a red background color. Select the view and enter the following values in the Size Inspector.
Select the red View and select the Auto Layout Add New Constraints button. Pin the Button to the top and and left. Select the width and height constraints. Select “Add 4 constraints.
Finally, add a Button to the main View and place it below the red View somewhere in the centre. Give it a title of "Move". The storyboard should look like this.
Select the Assistant Editor and make sure the ViewController.swift is visible. Ctrl + drag from the redView to the ViewController class and create the following outlet.
Ctrl + drag from the leading Constraint from the storyboard to the ViewController class and create the following Outlet.
Ctrl + drag from the Move Button from the Storyboard to the ViewController class and create the following Action.
Next, implement the moveButtonPressed Action method
@IBAction func moveButtonPressed(_ sender: Any) { // 1 let newConstraint = NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: self.view.frame.width/2) // 2 UIView.animate(withDuration: 2.0, delay: 0.0, options: .curveEaseOut , animations: { self.view.removeConstraint(self.leadingConstraint) self.view.addConstraint(newConstraint) self.view.layoutIfNeeded() }, completion: nil) // 3 leadingConstraint = newConstraint }
Using the NSLayoutConstraint initializer method, the leading constraint is set to the width of the main view, which puts the red image offscreen.
The view is animated by removing the old and adding the new constraint. The layoutIfNeeded method is used to force the layout before drawing.
The leading Contstraint is set to the new Constraint.
Build and Run the project, click the move button to move the red view offscreen.
You can download the source code of the IOS12AnimateViewsTutorial at the ioscreator repository on Github.
0 notes
Text
Recreating the Apple Music Now Playing transition
A common visual pattern in many iPhone apps is stacks of cards that slide in from the edge of the screen. You can see this in apps like Reminders, where the lists are represented by a stack of cards that spring up from the bottom. The Music app does this as well, where the current song expands from a mini player to a full screen card.
These animations can seem simple when examined in a casual fashion. But if you look closer, you’ll see there’s actually many things happening that make up the animation. Good animations are like good special effects in movies: they should go almost unnoticed.
In this tutorial, you are going to reproduce the Music app’s transition from mini-player to full-screen card. To keep things clean, you’ll use ordinary UIKit APIs.
To follow along with this tutorial, you’ll need the following:
Xcode 9.2 or later.
Familiarity with Auto Layout concepts.
Experience with creating and modifying UI and Auto Layout constraints within Interface Builder.
Experience with connecting IBOutlets in code to Interface Builder entities.
Experience with UIView animation APIs.
Getting Started
Download the starter project for this tutorial here.
Build and run the app. This app is RazePlayer, which provides a simple music catalog UI. Touch any song in the collection view to load the mini player at the bottom with that song. The mini player won’t actually play the song, which might be a good thing judging by the playlist!
Introducing the Storyboard
The starter project includes a full set of semi-complete view controllers so you can spend your time concentrating on creating the animation. Open Main.storyboard in the Project navigator to see them.
Use the iPhone 8 Plus simulator for this tutorial so the starter views make sense.
Have a look at the storyboard from left to right:
Tab Bar Controller with SongViewController: This is the collection view you see when you launch the app. It has a repeating collection of fake songs.
Mini Player View Controller: This view controller is embedded as a child of SongViewController. This is the view you’ll be animating from.
Maxi Song Card View Controller: This view will display the final state of the animation. Along with the storyboard, it’s the class you’ll be working with most.
Song Play Control View Controller: You’ll use this as part of the animation.
Expand the project in the project navigator. The project uses a normal Model-View-Controller pattern to keep data logic outside of the view controllers. The file you’ll be using most frequently is Song.swift, which represents a single song from the catalog.
You can explore these files later if you’re curious, but you don’t need to know what’s inside for this tutorial. Instead, you’ll be working with the following files in the View Layer folder:
Main.storyboard: Contains all the UI for the project.
SongViewController.swift: The main view controller.
MiniPlayerViewController.swift: Shows the currently selected song.
MaxiSongCardViewController.swift: Displays the card animation from mini player to maxi player.
SongPlayControlViewController.swift: Provides extra UI for the animation.
Take a moment to examine the transition in Apple’s Music app from the mini player to the large card. The album art thumbnail animates continuously into a large image, and the tab bar animates down and away. It might be hard to spot all the effects that contribute to this animation in real time. Fortunately, you’ll animate things in slow motion as you recreate this animation.
Your first task will be to jump from the mini player to the full-screen card.
Animating the Background Card
iOS animations often involve smoke and mirrors that fool users’ eyes into thinking what they are seeing is real. Your first task will be to make it appear the underlying content shrinks.
Creating a Fake Background
Open Main.storyboard and expand Maxi Song Card View Controller. The two views you’re going to work with are Backing Image View and Dimmer Layer
Open MaxiSongCardViewController.swift and add the following properties to the class, below the dimmerLayer outlet:
//add backing image constraints here @IBOutlet weak var backingImageTopInset: NSLayoutConstraint! @IBOutlet weak var backingImageLeadingInset: NSLayoutConstraint! @IBOutlet weak var backingImageTrailingInset: NSLayoutConstraint! @IBOutlet weak var backingImageBottomInset: NSLayoutConstraint!
Next, open Main.storyboard in the assistant editor by holding down the Option key and clicking Main.storyboard in the project navigator. You should now have MaxiSongCardViewController.swift open on the left and Main.storyboard on the right. The other way ’round is OK too if you’re in the southern hemisphere.
Next, connect the backing image IBOutlet's to the storyboard objects as shown below:
Expand the top level view of MaxiSongCardViewController and its top level constraints.
Connect backingImageTopInset to the top constraint of the Backing Image View.
Connect backingImageBottomInset to the bottom constraint of the Backing Image View.
Connect backingImageLeadingInset to the leading constraint of the Backing Image View.
Connect backingImageTrailingInset to the trailing constraint of the Backing Image View.
You’re now ready to present MaxiSongCardViewController. Close the assistant editor by pressing Cmd + Return or, alternately, View ▸ Standard Editor ▸ Show Standard Editor.
Open SongViewController.swift. First, add the following extension to the bottom of the file:
extension SongViewController: MiniPlayerDelegate { func expandSong(song: Song) { //1. guard let maxiCard = storyboard?.instantiateViewController( withIdentifier: "MaxiSongCardViewController") as? MaxiSongCardViewController else { assertionFailure("No view controller ID MaxiSongCardViewController in storyboard") return } //2. maxiCard.backingImage = view.makeSnapshot() //3. maxiCard.currentSong = song //4. present(maxiCard, animated: false) } }
When you tap the mini player, it delegates that action back up to the SongViewController. The mini player should neither know nor care what happens to that action.
Let’s go over this step-by-step:
Instantiate MaxiSongCardViewController from the storyboard. You use an assertionFailure within the guard statement to ensure you catch setup errors at design time.
Take a static image of the SongViewController and pass it to the new view controller. makeSnapshot is a helper method provided with the project.
The selected Song object is passed to the MaxiSongCardViewController instance
Present the controller modally with no animation. The presented controller will own its animation sequence.
Next, find the function prepare(for:sender:) and add the following line after miniPlayer = destination:
miniPlayer?.delegate = self
Build and run app, select a song from the catalog, then touch the mini player. You should get an instant blackout. Success!
You can see the status bar has vanished. You’ll fix that now.
Changing the Status Bar’s Appearance
The presented controller has a dark background, so you’re going to use a light style for the status bar instead. Open MaxiSongCardViewController.swift and add the following code to the MaxiSongCardViewController class;
override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }
Build and run app, tap a song then tap the mini player to present the MaxiSongCardViewController. The status bar will now be white-on-black.
The last task in this section is to create the illusion of the controller falling away to the background.
Shrinking the View Controller
Open MaxiSongCardViewController.swift and add the following properties to the top of the class:
let primaryDuration = 4.0 //set to 0.5 when ready let backingImageEdgeInset: CGFloat = 15.0
This provides the duration for the animation as well as the inset for the backing image. You can speed up the animation later, but for now it will run quite slowly so you can see what’s happening.
Next, add the following extension to the end of the file:
//background image animation extension MaxiSongCardViewController { //1. private func configureBackingImageInPosition(presenting: Bool) { let edgeInset: CGFloat = presenting ? backingImageEdgeInset : 0 let dimmerAlpha: CGFloat = presenting ? 0.3 : 0 let cornerRadius: CGFloat = presenting ? cardCornerRadius : 0 backingImageLeadingInset.constant = edgeInset backingImageTrailingInset.constant = edgeInset let aspectRatio = backingImageView.frame.height / backingImageView.frame.width backingImageTopInset.constant = edgeInset * aspectRatio backingImageBottomInset.constant = edgeInset * aspectRatio //2. dimmerLayer.alpha = dimmerAlpha //3. backingImageView.layer.cornerRadius = cornerRadius } //4. private func animateBackingImage(presenting: Bool) { UIView.animate(withDuration: primaryDuration) { self.configureBackingImageInPosition(presenting: presenting) self.view.layoutIfNeeded() //IMPORTANT! } } //5. func animateBackingImageIn() { animateBackingImage(presenting: true) } func animateBackingImageOut() { animateBackingImage(presenting: false) } }
Let’s go over this step-by-step:
Set the desired end position of the image frame. You correct the vertical insets with the aspect ratio of the image so the image doesn’t look squashed.
The dimmer layer is a UIView above the Image View with a black background color. You set the alpha on this to dim the image slightly.
You round off the corners of the image.
Using the simplest UIView animation API, you tell the image view to animate into its new layout. When animating Auto Layout constraints you must make a call to layoutIfNeeded() within the block or the animation will not run.
Provide public accessors to keep your code clean.
Next, add the following to viewDidLoad() after the call to super:
backingImageView.image = backingImage
Here you install the snapshot you passed through from SongViewController previously.
Finally add the following to the end of viewDidAppear(_:):
animateBackingImageIn()
Once the view appears, you tell the animation to start.
Build and run the app, select a song and then touch the mini player. You should see the current view controller receding into the background…very…slowly…
Awesome stuff! That takes care of one part of the sequence. The next significant part of the animation is growing the thumbnail image in the mini player into the large top image of the card.
Growing the Song Image
Open Main.storyboard and expand its view hierarchy again.
You’re going to be focusing on the following views:
Cover Image Container: This is a UIView with a white background. You’ll be animating its position in the scroll view.
Cover Art Image: This is the UIImageView you’re going to transition. It has a yellow background so it’s easier to see and grab in Xcode. Note the following two things about this view:
The Aspect Ratio is set to 1:1. This means it’s always a square.
The height is constrained to a fixed value. You’ll learn why this is in just a bit.
Open MaxiSongCardViewController.swift. You can see the outlets for the two views and dismiss button are already connected:
//cover image @IBOutlet weak var coverImageContainer: UIView! @IBOutlet weak var coverArtImage: UIImageView! @IBOutlet weak var dismissChevron: UIButton!
Next, find viewDidLoad(), and delete the following lines:
//DELETE THIS LATER scrollView.isHidden = true
This makes the UIScrollView visible. It was hidden previously so you could see what was going on with the background image.
Next, add the following lines to the end of viewDidLoad():
coverImageContainer.layer.cornerRadius = cardCornerRadius coverImageContainer.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
This sets corner radii for the top two corners only.
Build and run the app, tap the mini player and you’ll see you now see the container view and image view displayed above the background image snapshot.
Also notice that the image view has rounded corners. This was accomplished without code; instead, it was done via the User Defined Runtime Attributes panel.
Configuring the Cover Image Constraints
In this part you are going to add the constraints needed to animate the cover image display.
Open MaxiSongCardViewController.swift. Next, add the following constraints:
//cover image constraints @IBOutlet weak var coverImageLeading: NSLayoutConstraint! @IBOutlet weak var coverImageTop: NSLayoutConstraint! @IBOutlet weak var coverImageBottom: NSLayoutConstraint! @IBOutlet weak var coverImageHeight: NSLayoutConstraint!
Next, open Main.storyboard in the assistant editor and connect the outlets as follows:
Connect coverImageLeading, coverImageTop and coverImageBottom to the leading, top and bottom constraints of the Image View.
Connect coverImageHeight to the height constraint of the Image View.
The last constraint to add is the distance from the top of the cover image container to the content view of the scroll view.
Open MaxiSongCardViewController.swift. Next, add the following property to the class declaration:
//cover image constraints @IBOutlet weak var coverImageContainerTopInset: NSLayoutConstraint!
Finally, connect coverImageContainerTopInset to the top inset of the cover image container; this is the constraint with the constant parameter of 57, visible in Interface Builder.
Now all the constraints are set up to perform the animation.
Build and run the app; tap a song then tap the mini player to make sure everything is working fine.
Creating a Source Protocol
You need to know the starting point for the animation of the cover image. You could pass a reference of the mini player to the maxi player to derive all the necessary information to perform this information, but that would create a hard dependency between MiniPlayerViewController and MaxiSongCardViewController. Instead, you’ll add a protocol to pass the information.
Close the assistant editor and add the following protocol to the top of MaxiSongCardViewController.swift:
protocol MaxiPlayerSourceProtocol: class { var originatingFrameInWindow: CGRect { get } var originatingCoverImageView: UIImageView { get } }
Next, open MiniPlayerViewController.swift and add the following code at the end of the file:
extension MiniPlayerViewController: MaxiPlayerSourceProtocol { var originatingFrameInWindow: CGRect { let windowRect = view.convert(view.frame, to: nil) return windowRect } var originatingCoverImageView: UIImageView { return thumbImage } }
This defines a protocol to express the information the maxi player needs to animate. You then made MiniPlayerViewController conform to that protocol by supplying that information. UIView has built in conversion methods for rectangles and points that you’ll use a lot.
Next, open MaxiSongCardViewController.swift and add the following property to the main class:
weak var sourceView: MaxiPlayerSourceProtocol!
The reference here is weak to avoid retain cycles.
Open SongViewController.swift and add the following line to expandSong before the call to present(_, animated:):
maxiCard.sourceView = miniPlayer
Here you pass the source view reference to the maxi player at instantiation.
Animating in From the Source
In this section, you’re going to glue all your hard work together and animate the image view into place.
Open MaxiSongCardViewController.swift. Add the following extension to the file:
//Image Container animation. extension MaxiSongCardViewController { private var startColor: UIColor { return UIColor.white.withAlphaComponent(0.3) } private var endColor: UIColor { return .white } //1. private var imageLayerInsetForOutPosition: CGFloat { let imageFrame = view.convert(sourceView.originatingFrameInWindow, to: view) let inset = imageFrame.minY - backingImageEdgeInset return inset } //2. func configureImageLayerInStartPosition() { coverImageContainer.backgroundColor = startColor let startInset = imageLayerInsetForOutPosition dismissChevron.alpha = 0 coverImageContainer.layer.cornerRadius = 0 coverImageContainerTopInset.constant = startInset view.layoutIfNeeded() } //3. func animateImageLayerIn() { //4. UIView.animate(withDuration: primaryDuration / 4.0) { self.coverImageContainer.backgroundColor = self.endColor } //5. UIView.animate(withDuration: primaryDuration, delay: 0, options: [.curveEaseIn], animations: { self.coverImageContainerTopInset.constant = 0 self.dismissChevron.alpha = 1 self.coverImageContainer.layer.cornerRadius = self.cardCornerRadius self.view.layoutIfNeeded() }) } //6. func animateImageLayerOut(completion: @escaping ((Bool) -> Void)) { let endInset = imageLayerInsetForOutPosition UIView.animate(withDuration: primaryDuration / 4.0, delay: primaryDuration, options: [.curveEaseOut], animations: { self.coverImageContainer.backgroundColor = self.startColor }, completion: { finished in completion(finished) //fire complete here , because this is the end of the animation }) UIView.animate(withDuration: primaryDuration, delay: 0, options: [.curveEaseOut], animations: { self.coverImageContainerTopInset.constant = endInset self.dismissChevron.alpha = 0 self.coverImageContainer.layer.cornerRadius = 0 self.view.layoutIfNeeded() }) } }
Let’s go over this step-by-step:
Get the start position based on the location of the source view, less the vertical offset of the scroll view.
Place the container in its start position.
Animate the container to its finished position.
The first animation fades in the background color to avoid a sharp transition.
The second animation changes the top inset of the container and fades the dismiss button in.
Animate the container back to its start position. You’ll use this later. It reverses the animateImageLayerIn sequence.
Next, add the following to the end of viewDidAppear(_:):
animateImageLayerIn()
This adds the animation to the timeline.
Next, add the following to the end of viewWillAppear(_:):
configureImageLayerInStartPosition()
Here you set up the start position before the view appears. This lives in viewWillAppear so the change in start position of the image layer isn’t seen by the user.
Build and run the app, and tap the mini player to present the maxi player. You’ll see the container rise into place. It won’t change shape just yet because the container depends on the height of the image view.
Your next task is to add the shape change and animate the image view into place.
Animating From the Source Image
Open MaxiSongCardViewController.swift and add the following extension to the end of the file:
//cover image animation extension MaxiSongCardViewController { //1. func configureCoverImageInStartPosition() { let originatingImageFrame = sourceView.originatingCoverImageView.frame coverImageHeight.constant = originatingImageFrame.height coverImageLeading.constant = originatingImageFrame.minX coverImageTop.constant = originatingImageFrame.minY coverImageBottom.constant = originatingImageFrame.minY } //2. func animateCoverImageIn() { let coverImageEdgeContraint: CGFloat = 30 let endHeight = coverImageContainer.bounds.width - coverImageEdgeContraint * 2 UIView.animate(withDuration: primaryDuration, delay: 0, options: [.curveEaseIn], animations: { self.coverImageHeight.constant = endHeight self.coverImageLeading.constant = coverImageEdgeContraint self.coverImageTop.constant = coverImageEdgeContraint self.coverImageBottom.constant = coverImageEdgeContraint self.view.layoutIfNeeded() }) } //3. func animateCoverImageOut() { UIView.animate(withDuration: primaryDuration, delay: 0, options: [.curveEaseOut], animations: { self.configureCoverImageInStartPosition() self.view.layoutIfNeeded() }) } }
This code is similar to the image container animation from the previous section. Let’s go over this step-by-step:
Place the cover image in its start position using information from the source view.
Animate the cover image into its end position. The end height is the container width less its insets. Since the aspect ratio is 1:1, that will be its width as well.
Animate the cover image back to its start position for the dismissal action.
Next, add the following to the end of viewDidAppear(_:):
animateCoverImageIn()
This fires off the animation once the view is on screen.
Next, add the following lines to the end of viewWillAppear(_:):
coverArtImage.image = sourceView.originatingCoverImageView.image configureCoverImageInStartPosition()
This uses the UIImage from the source to populate the image view. It works in this particular case, because the UIImage has sufficient resolution so the image will not appear pixelated or stretched.
Build and run the app, the image view now grows from the source thumbnail and changes the frame of the container view at the same time.
Adding the Dismissal Animations
The button at the top of the card is connected to dismissAction(_:). Currently, it simply performs a modal dismiss action with no animation.
Just like you did when presenting the view controller, you want MaxiSongCardViewController to handle its own dismiss animation.
Open MaxiSongCardViewController.swift and replace dismissAction(_:) with the following:
@IBAction func dismissAction(_ sender: Any) { animateBackingImageOut() animateCoverImageOut() animateImageLayerOut() { _ in self.dismiss(animated: false) } }
This plays out the reverse animations that you set up previously in animating from source image. Once the animations have completed, you dismiss the MaxiSongCardViewController.
Build and run the app, bring up the maxi player and touch the dismiss control. The cover image and container view reverse back into the mini player. The only visible evidence of the dismissal is the Tab bar flickering in. You’ll fix this soon.
Displaying Song Information
Have a look at the Music app again and you’ll notice the expanded card contains a scrubber and volume control, information about the song, artist, album and upcoming tracks. This isn’t all contained in one single view controller — it’s built from components.
Your next task will be to embed a view controller in the scroll view. To save you time, there’s a controller all ready for you: SongPlayControlViewController.
Embedding the Child Controller
The first task is to detach the bottom of the image container from the scroll view.
Open Main.storyboard. Delete the constraint which binds the bottom of the cover image container to the bottom of the superview. You’ll get some red layout errors that the scroll view needs constraints for Y position or height. That’s OK.
Next, you’re going to setup a child view controller to display the song details by following the instructions below:
Add a Container View as a subview of Scroll View.
Ensure the Container View is above Stretchy Skirt in the view hierarchy (which requires it be below the Stretchy Skirt view in the Interface Builder Document Outline.
Another view controller will be added with a segue connection. Delete that new view controller.
Now add the following constraints to the new container view:
Leading, trailing and bottom. Pin to the scroll view and make them equal to 0.
Top to Cover Image Container bottom = 30
You may find it helpful to first adjust the view’s Y position, so that it is positioned below the image container view where it will be easier to define the constraints.
Lastly, bind the Container View embed segue to the SongPlayControlViewController. Hold down Control and drag from the container view to SongPlayControlViewController.
Release the mouse, and choose Embed from the menu that appears.
Finally, constrain the height of the Container view within the scroll view to unambiguously define the height of the scroll view’s content.
Select the Container View.
Open the Add New Constraints popover.
Set Height to 400. Tick the height constraint.
Press Add 1 Constraint.
At this stage, all the Auto Layout errors should be gone.
Animating the Controls
The next effect will raise the controls from the bottom of the screen to join the cover image at the end of the animation.
Open MaxiSongCardViewController.swift in the standard editor and Main.storyboard in the assistant editor.
Add the following property to the main class of MaxiSongCardViewController:
//lower module constraints @IBOutlet weak var lowerModuleTopConstraint: NSLayoutConstraint!
Attach the outlet to the constraint separating the image container and the Container View.
Close the assistant editor and add the following extension to the end of MaxiSongCardViewController.swift:
//lower module animation extension MaxiSongCardViewController { //1. private var lowerModuleInsetForOutPosition: CGFloat { let bounds = view.bounds let inset = bounds.height - bounds.width return inset } //2. func configureLowerModuleInStartPosition() { lowerModuleTopConstraint.constant = lowerModuleInsetForOutPosition } //3. func animateLowerModule(isPresenting: Bool) { let topInset = isPresenting ? 0 : lowerModuleInsetForOutPosition UIView.animate(withDuration: primaryDuration, delay:0, options: [.curveEaseIn], animations: { self.lowerModuleTopConstraint.constant = topInset self.view.layoutIfNeeded() }) } //4. func animateLowerModuleOut() { animateLowerModule(isPresenting: false) } //5. func animateLowerModuleIn() { animateLowerModule(isPresenting: true) } }
This extension performs a simple animation of the distance between SongPlayControlViewController‘s view and the Image container as follows:
Calculates an arbitrary distance to start from. The height of the view less the width is a good spot.
Places the controller in its start position.
Performs the animation in either direction.
A helper method that animates the controller into place.
Animates the controller out.
Now to add this animation to the timeline. First, add the following to the end of viewDidAppear(_:):
animateLowerModuleIn()
Next, add the following to the end of viewWillAppear(_:).
stretchySkirt.backgroundColor = .white //from starter project, this hides the gap configureLowerModuleInStartPosition()
Next, add this line to dismissAction(_:) before the call to animateImageLayerOut(completion:), for the dismissal animation:
animateLowerModuleOut()
Finally, add the following to MaxiSongCardViewController.swift to pass the current song across to the new controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let destination = segue.destination as? SongSubscriber { destination.currentSong = currentSong } }
This checks if the destination conforms to SongSubscriber then passes the song across. This is a simple demonstration of dependency injection.
Build and run the app. Present the maxi player and you’ll see the SongPlayControl’s view rise into place.
Hiding the Tab Bar
The last thing to do before you finish is to deal with the Tab bar. You could possibly hack the frame of the tab bar, but that would create some messy interactions with the active view controller frame. Instead, you’ll need a bit more smoke and a few more mirrors:
Take a snapshot image of the Tab bar.
Pass it through to the MaxiSongCardViewController.
Animate the tab bar snapshot image.
First, add the following to MaxiSongCardViewController:
//fake tabbar contraints var tabBarImage: UIImage? @IBOutlet weak var bottomSectionHeight: NSLayoutConstraint! @IBOutlet weak var bottomSectionLowerConstraint: NSLayoutConstraint! @IBOutlet weak var bottomSectionImageView: UIImageView!
Next, open Main.storyboard and drag an Image View into the MaxiSongCardViewController view hierarchy. You want it to be above the scroll view in the view hierarchy (which means below it, in Interface Builder’s navigator).
Using the Add Constraints popover, Untick Constrain to margins. Pin its leading, trailing and bottom edges to the superview with size 0. This will, in fact, pin to the safe area. Add a height constraint of 128, and press Add 4 Constraints to commit the changes.
Next, open MaxiSongCardViewController.swift in the assistant editor and connect the three properties you added to the Image view.
bottomSectionImageView connects to the Image View.
bottomSectionLowerConstraint connects to the Bottom constraint.
bottomSectionHeight connects to the height constraint.
Finally, close the assistant editor, and add the following extension to the end of MaxiSongCardViewController.swift:
//fake tab bar animation extension MaxiSongCardViewController { //1. func configureBottomSection() { if let image = tabBarImage { bottomSectionHeight.constant = image.size.height bottomSectionImageView.image = image } else { bottomSectionHeight.constant = 0 } view.layoutIfNeeded() } //2. func animateBottomSectionOut() { if let image = tabBarImage { UIView.animate(withDuration: primaryDuration / 2.0) { self.bottomSectionLowerConstraint.constant = -image.size.height self.view.layoutIfNeeded() } } } //3. func animateBottomSectionIn() { if tabBarImage != nil { UIView.animate(withDuration: primaryDuration / 2.0) { self.bottomSectionLowerConstraint.constant = 0 self.view.layoutIfNeeded() } } } }
This code is similar to the other animations. You’ll recognize all the sections.
Set up the image view with the supplied image, or collapse to zero height in the case of no image.
Drop the image view below the edge of the screen.
Lift the image view back into the normal position.
The last thing to do in this file is add the animations to the timeline.
First, add the following to the end of viewDidAppear(_:):
animateBottomSectionOut()
Next, add the following to the end of viewWillAppear(_:):
configureBottomSection()
Next, add the following to dismissAction(_:) before the call to animateImageLayerOut(completion:):
animateBottomSectionIn()
Next, open SongViewController.swift and add the following code before the call to present(animated:) in expandSong(song:):
if let tabBar = tabBarController?.tabBar { maxiCard.tabBarImage = tabBar.makeSnapshot() }
Here you take a snapshot of the Tab bar, if it exists, and then pass it through to MaxiSongCardViewController.
Finally, open MaxiSongCardViewController.swift and change the primaryDuration property to 0.5 so you don’t have to be tortured by the slow animations anymore!
Build and run the app, present the maxi player, and the tab bar will rise and fall into place naturally.
Congratulations! You’ve just completed a recreation of the card animation that closely resembles the one in the Music app.
Where to Go From Here
You can download the finished version of the project here.
In this tutorial, you learned all about the following:
Animating Auto Layout constraints.
Placing multiple animations into a timeline to composite a complex sequence.
Using static snapshots of views to create the illusion of change.
Using the delegate pattern to create weak bindings between objects.
Note that the method of using a static snapshot would not work where the underlying view changes while the card is being presented, such as in the case where an asynchronous event causes a reload.
Animations are costly in terms of development time, and they’re hard to get just right. However, it’s usually worth the effort, as they add an extra element of delight and can turn an ordinary app into an extraordinary one.
Hopefully this tutorial has triggered some ideas for your own animations. If you have any comments or questions, or want to share your own creations, come join the discussion below!
The post Recreating the Apple Music Now Playing transition appeared first on Ray Wenderlich.
Recreating the Apple Music Now Playing transition published first on https://medium.com/@koresol
0 notes
Text
80% off #Advanced iOS Instruction: Clone WhatsApp with Bitfountain – $10
280 video lectures that take you step by step through the process of creating a complete WhatsApp clone with Firebase
Expert Level, – 13 hours, 289 lectures
Average rating 4.6/5 (4.6 (73 ratings) Instead of using a simple lifetime average, Udemy calculates a course’s star rating by considering a number of different factors such as the number of ratings, the age of ratings, and the likelihood of fraudulent ratings.)
Course requirements:
Knowledge of Swift and iOS Development similar to what is covered in our iOS Development Course. Ability to set up and use Core Data to a basic level. See our Core Data course. Understand how to use Auto Layout to create responsive views. See our Auto layout course.
Course description:
Course Description
Our WhaleTalk course teaches you how to build a complete WhatsApp clone in Swift 2.0 and iOS 9. This is not a toy app. You will be building a chat view controller that is fully responsive with auto layout – all from scratch. The chat functionality goes beyond person to person. Just like in WhatsApp, you will have the ability to start group chats and import contacts with the Contacts Framework. Data will be persisted to Core Data, and it will all be synced to the cloud with Firebase.
We believe students learn by building. There’s no better way to become an iOS developer than building a complex app from scratch.
Student Reviews:
“I must say that so far, this course is awesome. Having the challenging assignments, daily discussions and feedback from the instructors, has been the missing piece that I have been looking for. I have read a handful of books, watched hours of video & typed in a bunch of tutorials, and finally, having to work through tough assignments and applying what I have been learning, it is all starting to click – Finally!” – Mark S.
“This course is by far the most elaborate and best taught iOS course I have seen online yet. It’s good structured and covers a lot of topics in-depth.” – Christoph Zamaitat
“Bitfountain’s discussion board is one of the best resources for a beginning iOS developer. So much help being offered” – Omar S.
“Great course for total beginners, but also a lot of tricks and tips for those with experience. Also good seeing how others code and tackle problems. A great learning tool what ever your skill level.” – Mazza1111
“I’ve just completed the iOS course, which I thought was a great intro to the XCode environment… I feel it’s been well worth the investment. ” – Herdy H.
“I am about a quarter of the way through this course and have no previous programming experience. I have found this course to be well presented and structured with everything explained clearly. This is a difficult topic and you have to work hard understanding the concepts if you are new to it, but it is easy to go back over an area to pick up anything you might have missed first time round. The guys are constantly improving it and adding to it and seem committed to getting it 100% right. Recommend it….but be prepared to work hard!!” – Tony McDonald
“Can’t reiterate it enough how this course is helping me with my iOS dev skills. I think using protocols and delegation is finally becoming second nature. Fingers crossed :-)” -Alex P.
“I am really loving the class. I have taken classes at Code School & Treehouse and both were missing a key element. The ability to ask questions as you complete a section and get an answer. ” -Lisa A.
“Your training is the best out there so far. I wish I had the time away from regular job to follow along.” -Christian S.
“Im loving this.. I have been through at least 5 books, and many online deals. Yours is super so far. Finally, i can get from start to finish on a lesson without wondering why on “Everything” thank you. Cant wait to do more.. ” -Kevin R.
Full details How to architect an advanced application. Creating complex views. Modeling, updating, and reading dynamic data. How to keep multiple devices synced through the cloud with Firebase. Persisting data with Core Data. Importing contacts with the Contacts Framework.
Full details Anyone interested in learning how to build a complex app from beginning to end.
Reviews:
“I will give 4/5 stars, because this course is lack of layout constraints explanation. Author just dive into coding a lot of NSLayoutConstraint in one go without any mockup planning or drawing. Otherwise, the course content is very helpful. I really love it.” (Yuber Ng)
“Interesting content! And very advanced and talented teacher!” (Alexey Gofman)
“EXCELLENT course! Great presentation and great material of more advanced concepts in iOS. The code is presented in bite size chunks with very solid explanations following immediately behind. This method really helps absorb the information and also makes it easy to review material if desired. Highly recommended.” (A Jensen)
About Instructor:
Mr. Eliot Arntz Mr. John Omar
Eliot regularly teaches iOS development classes and workshops at General Assembly and guest lectures for companies and development boot camps around NYC. He also taught the inaugural class for Coalition for Queens which focused on increasing diversity in iOS development. He also coaches students in a one-on-one environment with a focus on transitioning to full time development. Eliot cofounded and organizes the iOS Office Hours meetup NYC. In his free time he works as a contractor for startups focusing on agile development. Find me on twitter @EliotArntz – I check my direct messages quite frequently.
John was the lead iOS developer at Fast Society and Cameo until he started Bitfountain in 2012. The apps that John has contributed to have appeared in TechCrunch, Mashable and the New York Times. At Bitfountain, John oversees all projects from a technical and strategic perspective. At Bitfountain, John has taught over 120,000 students how to code online.
Instructor Other Courses:
Bitfountain Objective-C for iOS 9 Mr. John Omar, Designer, developer, teacher at Bitfountain (8) $10 $20 Bitfountain Objective-C for iOS 9 Mr. John Omar, Designer, developer, teacher at Bitfountain (8) $10 $20 Bitfountain iOS Design Foundations Million Dollar Instructor Million Dollar Instructor …………………………………………………………… Mr. Eliot Arntz Mr. John Omar coupons Development course coupon Udemy Development course coupon Mobile Apps course coupon Udemy Mobile Apps course coupon Advanced iOS Instruction: Clone WhatsApp with Bitfountain Advanced iOS Instruction: Clone WhatsApp with Bitfountain course coupon Advanced iOS Instruction: Clone WhatsApp with Bitfountain coupon coupons
The post 80% off #Advanced iOS Instruction: Clone WhatsApp with Bitfountain – $10 appeared first on Course Tag.
from Course Tag http://coursetag.com/udemy/coupon/80-off-advanced-ios-instruction-clone-whatsapp-with-bitfountain-10/ from Course Tag https://coursetagcom.tumblr.com/post/158276023388
0 notes
Text
How to: NSLayoutConstraint crashes ViewController
How to: NSLayoutConstraint crashes ViewController
NSLayoutConstraint crashes ViewController
Possible Duplicate: presentViewController: crash on iOS 6 (AutoLayout)
I’m getting this error when clicking on a button in my app:
2012-06-28 21:43:36.860 AppName[2403:707] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named NSLayoutConstraint' *** First throw call stack:…
View On WordPress
0 notes
Video
youtube
my review point is 10/10
NSLayoutConstraint 사용법의 예시 (길이10분정도)
// ViewController.swift
// boundsAndFrame
//
// Created by AJ Norton on 8/27/15.
// Copyright (c) 2015 AJ Norton. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let v1 = UIView(frame: CGRectMake(50, 50, 300, 200))
v1.backgroundColor = UIColor.redColor()
self.view.addSubview(v1)
let v2 = UIView()
v2.backgroundColor = UIColor.greenColor()
v2.setTranslatesAutoresizingMaskIntoConstraints(false)
v1.addSubview(v2)
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: v1,
attribute: .Left,
multiplier: 1,
constant: 0))
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: NSLayoutAttribute.Right,
relatedBy: NSLayoutRelation.Equal,
toItem: v1,
attribute: .Right,
multiplier: 1,
constant: 0))
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: v1,
attribute: .Top,
multiplier: 1,
constant: 0))
v1.addConstraint(
NSLayoutConstraint(item: v2,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: v1,
attribute: .Height,
multiplier: 0.5,
constant: 0))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
0 notes
Link
original source : http://www.knowstack.com/swift-nslayoutconstraint-programatically-sample-code/
my review point is 8/10
In this post we will apply constraints to 4 views in a NSWindow using swift NSLayoutConstraint programatically. This approach is much more tedious and lengthy in comparison to the constraintsWithVisualFormat Approach.
Its upto the developers discretion to either use the visual format or constrain initializer approach.
Note: The autoLayOut checkbox is switched off in the XIB and all the constraints will be added programmatically
In the below sample the following behaviour is desired
Red View – Header and is of constant height- width is resizable as the window resizes
Green View – Search Section is of constant height – width of the green view increases as the window resizes
Blue View – Data View is of dynamic width and height and increases as the screen resizes.
Cyan View – Footer section is of fixed height and dynamic width
The screen has a minimum width and height that is derived from the sections
Before applying constraint
Swift NSLayoutConstraint constraintsWithVisualFormat- after applying constraints programmatically
Sample Code
NSLayoutConstraint( item: AnyObject, attribute: NSLayoutAttribute, relatedBy: NSLayoutRelation, toItem: AnyObject?>, attribute: NSLayoutAttribute, multiplier: CGFloat, constant: CGFloat)
// AppDelegate.swift // NSLayoutConstraintSwiftSampleCode // Created by Debasis Das on 15/01/16. // Copyright © 2016 Knowstack. All rights reserved. import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! @IBOutlet weak var headerView: HeaderView! @IBOutlet weak var searchView: SearchView! @IBOutlet weak var dataView: DataView! @IBOutlet weak var footerView: FooterView! func applicationDidFinishLaunching(aNotification: NSNotification) { // Insert code here to initialize your application } func applicationWillTerminate(aNotification: NSNotification) { // Insert code here to tear down your application } override func awakeFromNib() { autolayoutUsingConstraint() } func autolayoutUsingConstraint(){ self.headerView.translatesAutoresizingMaskIntoConstraints = false self.searchView.translatesAutoresizingMaskIntoConstraints = false self.dataView.translatesAutoresizingMaskIntoConstraints = false self.footerView.translatesAutoresizingMaskIntoConstraints = false let mainView = self.window.contentView //HeaderView //Header = 20 from left edge of screen let cn1 = NSLayoutConstraint(item: headerView, attribute: .Leading, relatedBy: .Equal, toItem: mainView, attribute: .Leading, multiplier: 1.0, constant: 20) //Header view trailing end is 20 px from right edge of the screen let cn2 = NSLayoutConstraint(item: headerView, attribute: .Trailing, relatedBy: .Equal, toItem: mainView, attribute: .Trailing, multiplier: 1.0, constant: -20) //Header view height = constant 60 let cn3 = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 60) //Header view width greater than or equal to 400 let cn4 = NSLayoutConstraint(item: headerView, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 400) //Header view vertical padding from the top edge of the screen = 20 let cn5 = NSLayoutConstraint(item: headerView, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 20) //Search Section //search section 20px from left edge of screen let cn6 = NSLayoutConstraint(item: searchView, attribute: .Leading, relatedBy: .Equal, toItem: mainView, attribute: .Leading, multiplier: 1.0, constant: 20) //search section 20px from the right edge of the screen let cn7 = NSLayoutConstraint(item: searchView, attribute: .Trailing, relatedBy: .Equal, toItem: mainView, attribute: .Trailing, multiplier: 1.0, constant: -20) //search section height = constant 100 let cn8 = NSLayoutConstraint(item: searchView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100) //search section width >=400 let cn9 = NSLayoutConstraint(item: searchView, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 400) let cn10 = NSLayoutConstraint(item: searchView, attribute: .Top, relatedBy: .Equal, toItem: headerView, attribute: .Bottom, multiplier: 1.0, constant: 20) //Data Section let cn11 = NSLayoutConstraint(item: dataView, attribute: .Leading, relatedBy: .Equal, toItem: mainView, attribute: .Leading, multiplier: 1.0, constant: 20) let cn12 = NSLayoutConstraint(item: dataView, attribute: .Trailing, relatedBy: .Equal, toItem: mainView, attribute: .Trailing, multiplier: 1.0, constant: -20) //Data section height is >=300 and width is >=400 let cn13 = NSLayoutConstraint(item: dataView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 300) let cn14 = NSLayoutConstraint(item: dataView, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 400) let cn15 = NSLayoutConstraint(item: dataView, attribute: .Top, relatedBy: .Equal, toItem: searchView, attribute: .Bottom, multiplier: 1.0, constant: 20) //Footer Section let cn16 = NSLayoutConstraint(item: footerView, attribute: .Leading, relatedBy: .Equal, toItem: mainView, attribute: .Leading, multiplier: 1.0, constant: 20) let cn17 = NSLayoutConstraint(item: footerView, attribute: .Trailing, relatedBy: .Equal, toItem: mainView, attribute: .Trailing, multiplier: 1.0, constant: -20) let cn18 = NSLayoutConstraint(item: footerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50) let cn19 = NSLayoutConstraint(item: footerView, attribute: .Width, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 400) let cn20 = NSLayoutConstraint(item: footerView, attribute: .Top, relatedBy: .Equal, toItem: dataView, attribute: .Bottom, multiplier: 1.0, constant: 20) let cn21 = NSLayoutConstraint(item: footerView, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -20) mainView?.addConstraint(cn1) mainView?.addConstraint(cn2) mainView?.addConstraint(cn3) mainView?.addConstraint(cn4) mainView?.addConstraint(cn5) mainView?.addConstraint(cn6) mainView?.addConstraint(cn7) mainView?.addConstraint(cn8) mainView?.addConstraint(cn9) mainView?.addConstraint(cn10) mainView?.addConstraint(cn11) mainView?.addConstraint(cn12) mainView?.addConstraint(cn13) mainView?.addConstraint(cn14) mainView?.addConstraint(cn15) mainView?.addConstraint(cn16) mainView?.addConstraint(cn17) mainView?.addConstraint(cn18) mainView?.addConstraint(cn19) mainView?.addConstraint(cn20) mainView?.addConstraint(cn21) } } class HeaderView: NSView { override func drawRect(dirtyRect: NSRect) { super.drawRect(dirtyRect) NSColor.redColor().setFill() NSRectFill(dirtyRect); } } class SearchView: NSView { override func drawRect(dirtyRect: NSRect) { super.drawRect(dirtyRect) NSColor.greenColor().setFill() NSRectFill(dirtyRect); } } class DataView: NSView { override func drawRect(dirtyRect: NSRect) { super.drawRect(dirtyRect) NSColor.blueColor().setFill() NSRectFill(dirtyRect); } } class FooterView: NSView { override func drawRect(dirtyRect: NSRect) { super.drawRect(dirtyRect) NSColor.cyanColor().setFill() NSRectFill(dirtyRect); } }
The above sample code can be downloaded hereNSLayoutConstraint Swift Sample Code
Also Read constraintsWithVisualFormat
Simple Example on constraintsWithVisualFormat
Detailed Example on constraintsWithVisualFormat
0 notes
Text
Auto Layout Visual Format Language Tutorial
Update note: This tutorial has been updated to iOS 11, Xcode 9 and Swift 4 by József Vesza. The original tutorial was written by Darren Ferguson.
Learn how to layout elements using ASCII art!
The Auto Layout Visual Format Language (VFL) allows you to define constraints by using an ASCII-art formatted string.
With a single line of code, you can specify multiple constraints in either the horizontal or vertical direction. This can save a lot of code compared to creating constraints one at a time.
In this tutorial, you and VFL will become buddies as you do the following:
Construct horizontal and vertical constraints
Use views definitions inside your VFL string
Use metrics constants inside your VFL string
Use layout options to position interface elements relative to others
Use safe area to take the iPhone X into consideration
Note: this tutorial assumes you’re well acquainted with Auto Layout. If you’re fairly new to it, you may want to start with Auto Layout Tutorial in iOS 11: Getting Started.
Getting Started
Start by downloading the starter project for this tutorial, which comprises a basic welcome screen for a fledgling social networking app — Grapevine. Build and run (Product \ Run or ⌘R) the project in Xcode; you’ll see the following (to rotate the simulator go to Hardware \ Rotate Right):
Note: Please use of one of the rectangular iPhones (e.g., iPhone 8) for this part of the tutorial. You’ll see how to handle the iPhone X later.
Well, that’s a hot mess. Why is this happening and what are you going to do about it?
All the interface elements are currently pinned to the top and left of the view, and this is the result of them having no associated Auto Layout constraints. You’ll make the view much prettier during the course of this tutorial.
Open Main.storyboard and look at the interface elements. Note the interface elements are set with Auto Layout constraints that are removed at compile time. You wouldn’t do this in a real project, but this saves you having to enter a lot of view creation code :]
Next, open ViewController.swift and have a look inside. At the top, you’ll see outlets connected to the Interface Builder (IB) interface elements inside Main.storyboard.
There’s not much else to talk about in the app at this point, but there’s a lot good stuff to learn about VFL!
Visual Format String Grammar
Before you dive into setting up layouts and constraints, you’ll need some background knowledge on the VFL format string.
First thing to know: The format string can be split into the following components:
Here’s a step-by-step explanation of the VFL format string:
Direction of your constraints, not required. Can have the following values:
H: indicates horizontal orientation.
V: indicates vertical orientation.
Not specified: Auto Layout defaults to horizontal orientation.
Leading connection to the superview, not required.
Spacing between the top edge of your view and its superview’s top edge (vertical)
Spacing between the leading edge of your view and its superview’s leading edge (horizontal)
View you’re laying out, is required.
Connection to another view, not required.
Trailing connection to the superview, not required.
Spacing between the bottom edge of your view and its superview’s bottom edge (vertical)
Spacing between the trailing edge of your view and its superview’s trailing edge (horizontal)
There’s two special (orange) characters in the image and their definition is below:
? component is not required inside the layout string.
* component may appear 0 or more times inside the layout string.
Available Symbols
VFL uses a number of symbols to describe your layout:
| superview
- standard spacing (usually 8 points; value can be changed if it is the spacing to the edge of a superview)
== equal widths (can be omitted)
-20- non standard spacing (20 points)
<= less than or equal to
>= greater than or equal to
@250 priority of the constraint; can have any value between 0 and 1000
250 - low priority
750 - high priority
1000 - required priority
Example Format String
H:|-[icon(==iconDate)]-20-[iconLabel(120@250)]-20@750-[iconDate(>=50)]-|
Here's a step-by-step explanation of this string:
H: horizontal direction.
|-[icon icon's leading edge should have standard distance from its superview's leading edge.
==iconDate icon's width should be equal to iconDate's width.
]-20-[iconLabel icon's trailing edge should be 20 points from iconLabel's leading edge.
[iconLabel(120@250)] iconLabel should have a width of 120 points. The priority is set to low, and Auto Layout can break this constraint if a conflict arises.
-20@750- iconLabel's trailing edge should be 20 points from iconDate's leading edge. The priority is set to high, so Auto Layout shouldn't break this constraint if there's a conflict.
[iconDate(>=50)] iconDate's width should be greater than or equal to 50 points.
-| iconDate's trailing edge should have standard distance from its superview's trailing edge.
Now you have a basic understanding of the VFL -- and more importantly the format string -- it's time to put that knowledge to use.
Creating Constraints
Apple provides the class method constraints(withVisualFormat:options:metrics:views:) on NSLayoutConstraint to create constraints. You'll use this to create constraints programmatically inside of the Grapevine app.
Open ViewController.swift in Xcode, and add the following code:
override func viewDidLoad() { super.viewDidLoad() appImageView.isHidden = true welcomeLabel.isHidden = true summaryLabel.isHidden = true pageControl.isHidden = true }
This code hides all interface elements except the iconImageView, appNameLabel and skipButton. Build and run your project; you should see the following:
Cool. You've cleared the cluttery interface elements, now add the following code to the bottom of viewDidLoad():
// 1 let views: [String: Any] = [ "iconImageView": iconImageView, "appNameLabel": appNameLabel, "skipButton": skipButton] // 2 var allConstraints: [NSLayoutConstraint] = [] // 3 let iconVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:|-20-[iconImageView(30)]", metrics: nil, views: views) allConstraints += iconVerticalConstraints // 4 let nameLabelVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:|-23-[appNameLabel]", metrics: nil, views: views) allConstraints += nameLabelVerticalConstraints // 5 let skipButtonVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:|-20-[skipButton]", metrics: nil, views: views) allConstraints += skipButtonVerticalConstraints // 6 let topRowHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|", metrics: nil, views: views) allConstraints += topRowHorizontalConstraints // 7 NSLayoutConstraint.activate(allConstraints)
Here's a step-by-step explanation of the above code:
Create a views dictionary that holds string representations of views to resolve inside the format string.
Create a mutable array of constraints. You'll build this up in the rest of the code.
Set up vertical constraints for the iconImageView, placing its top edge 20 points from its superview's top edge, with a height of 30 points.
Set up vertical constraints for the appNameLabel, placing its top edge 23 points from its superview's top edge.
Set up vertical constraints for the skipButton, placing its top edge 20 points from its superview's top edge.
Set up horizontal constraints for all three interface elements. The iconImageView's leading edge is placed 15 points from the leading edge of its superview, with a width of 30 points. Next, a standard spacing of 8 points is placed between the iconImageView and appNameLabel. Next, a standard spacing of 8 points is placed between the appNameLabel and skipButton. Finally, the skipButton's trailing edge is placed 15 points from the trailing edge of its superview.
Activate the layout constraints using the class method activate(_:) on NSLayoutConstraint. You pass in the allConstraints array you've been adding to all this time.
Note: The string keys inside the views dictionary must match the view strings inside the format string. If they don't, Auto Layout won't be able to resolve the reference and will crash at runtime.
Build and run your project. How do the interface elements look now?
Hey, look at that! You've already made it prettier.
Now stick with it here, that first part was just a teaser. You've got a lot of code to write, but it'll be worth it at the end.
Next, you'll lay out the remaining interface elements. First, you need to remove the code you originally added to viewDidLoad(). I know, I know...you just put it there. Delete the following lines:
appImageView.isHidden = true welcomeLabel.isHidden = true summaryLabel.isHidden = true pageControl.isHidden = true
Removing this reverts the display so it shows the remaining interface elements that you previously hid from yourself.
Next, replace your current views dictionary definition with the following:
let views: [String: Any] = [ "iconImageView": iconImageView, "appNameLabel": appNameLabel, "skipButton": skipButton, "appImageView": appImageView, "welcomeLabel": welcomeLabel, "summaryLabel": summaryLabel, "pageControl": pageControl]
Here you've added view definitions for the appImageView, welcomeLabel, summaryLabel and pageControl, which can now be used inside the VFL format string.
Add the following to the bottom of viewDidLoad(), above the activate(_:) call:
// 1 let summaryHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-15-[summaryLabel]-15-|", metrics: nil, views: views) allConstraints += summaryHorizontalConstraints let welcomeHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-15-[welcomeLabel]-15-|", metrics: nil, views: views) allConstraints += welcomeHorizontalConstraints // 2 let iconToImageVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[iconImageView]-10-[appImageView]", metrics: nil, views: views) allConstraints += iconToImageVerticalConstraints // 3 let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[appImageView]-10-[welcomeLabel]", metrics: nil, views: views) allConstraints += imageToWelcomeVerticalConstraints // 4 let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]", metrics: nil, views: views) allConstraints += summaryLabelVerticalConstraints // 5 let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-15-|", metrics: nil, views: views) allConstraints += summaryToPageVerticalConstraints
Here's a step-by-step explanation of the above:
Set up horizontal constraints for the summaryLabel and welcomeLabel, placing them 15 points from the leading and trailing edges of their superview.
Set up vertical constraints for the icon to the app image, with spacing of 10 points
Set up vertical constraints for the app image to the welcome label, with spacing of 10 points
Set up vertical constraints between the welcome label and summary label, with a spacing of 4 points
Set up vertical constraints between the summary label and the page control, with a spacing of 15 points and a height of 9 points for the page control, then spacing of 15 points to the superview
Build and run your project; how do the interface elements look?
Now you're getting somewhere. No, it's not exactly what you're looking for; some interface elements are laid out correctly, however, others are not. The image and the page control aren't centered.
Never fear, the next section will provide you with more ammunition to get the layout to clean up its act.
Layout Options
Layout options provide the ability to manipulate view constraints perpendicular to the current layout orientation being defined.
Applying vertical centering to all views in a horizontal layout orientation by using NSLayoutFormatOptions.AlignAllCenterY is an example of layout options.
You wouldn't use this option in vertical orientation since there's no sense in trying to set all of the views' centers vertically while laying them out vertically, edge by edge. It's also not provided for vertical orientation, so there you go.
Next, you'll see how layout options are useful when it comes to constructing layouts. Remove the following code from viewDidLoad():
let nameLabelVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat( "V:|-23-[appNameLabel]", metrics: nil, views: views) allConstraints += nameLabelVerticalConstraints let skipButtonVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat( "V:|-20-[skipButton]", metrics: nil, views: views) allConstraints += skipButtonVerticalConstraints
You're just removing the vertical constraints from the appNameLabel and skipButton. Instead, you're going to use the layout options to give them a vertical position.
Find the code that creates topRowHorizontalConstraints and set the options parameter to [.alignAllCenterY]. It should now look like the following:
let topRowHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|", options: [.alignAllCenterY], metrics: nil, views: views) allConstraints += topRowHorizontalConstraints
You've provided the NSLayoutFormatOption .alignAllCenterY that takes each view inside the format string and creates layout constraints to align each of them by their vertical centers. This code works since the iconImageView has previously defined vertical layout constraints, including its height. Thus, the appNameLabel and skipButton are vertically centered with the iconImageView.
If you build and run now, the layout will look exactly the same, but you know the code is better :]
Remove the code that creates welcomeHorizontalConstraints and adds it to the constraints array.
This removes the horizontal constraints from the welcomeLabel.
Next, update the options when creating summaryLabelVerticalConstraints definition to the following:
let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views) allConstraints += summaryLabelVerticalConstraints
This code adds the NSLayoutFormatOptions options .alignAllLeading and .alignAllTrailing to the options array. The welcomeLabel and summaryLabel's leading and trailing spacing will be aligned 15 points from the leading and trailing edge of their superview. This occurs because the summaryLabel already has its horizontal constraints defined.
Again, this will give you the same layout as you already had, but in a better way.
Next, update the options when you create summaryToPageVerticalConstraints to match the following:
let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-15-|", options: [.alignAllCenterX], metrics: nil, views: views) allConstraints += summaryToPageVerticalConstraints
Adding this option aligns the views on the center X axis. Do the same for imageToWelcomeVerticalConstraints:
let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[appImageView]-10-[welcomeLabel]", options: [.alignAllCenterX], metrics: nil, views: views) allConstraints += imageToWelcomeVerticalConstraints
Build and run your project; how do the interface elements look?
Feeling centered yet? Layout options have taken you closer to that nice user interface you're after.
NSLayoutFormat Options Quick Reference
Here are the options you've used in Grapevine:
.alignAllCenterX -- align interface elements using NSLayoutAttribute.centerX.
.alignAllCenterY -- align interface elements using NSLayoutAttribute.centerY.
.alignAllLeading -- align interface elements using NSLayoutAttribute.leading.
.alignAllTrailing -- align interface elements using NSLayoutAttribute.trailing.
Below are some more of these options:
.alignAllLeft -- align interface elements using NSLayoutAttribute.left.
.alignAllRight -- align interface elements using NSLayoutAttribute.right.
.alignAllTop -- align interface elements using NSLayoutAttribute.top.
.alignAllBottom -- align interface elements using NSLayoutAttribute.bottom.
.alignAllLastBaseline -- align interface elements using NSLayoutAttribute.lastBaseline.
You can find the complete list in the documentation.
Note: At least one of the elements must have enough defined perpendicular constraints for layout options to work. See the example below:
NSLayoutConstraints.constraintsWithVisualFormat( "V:[topView]-[middleView]-[bottomView]", options: [.alignAllLeading], metrics: nil, views: ["topView": topView, "middleView": middleView, "bottomView": bottomView"])
The topView, middleView or bottomView must have constraints defining the position of their leading edge for Auto Layout to generate the correct constraints.
And now for a new concept! Meet Metrics.
Metrics
Metrics are a dictionary of number values that can appear inside the VFL format string. These are particularly useful if you have standardized spacing or calculated size values that you can't type directly into the format string.
Add the following constant declaration above your @IBOutlet declarations in ViewController.swift:
private enum Metrics { static let padding: CGFloat = 15.0 static let iconImageViewWidth: CGFloat = 30.0 }
Now you have a constant for the padding, and icon image width, you can create a metrics dictionary and utilize the constant. Add the following code above your views declaration in viewDidLoad():
let metrics = [ "horizontalPadding": Metrics.padding, "iconImageViewWidth": Metrics.iconImageViewWidth]
The above code creates a dictionary of key / value pairs to be substituted into the format string.
Next, replace the topRowHorizontalConstraints and summaryHorizontalConstraints definitions with the following:
let topRowHorizontalFormat = """ H:|-horizontalPadding-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-horizontalPadding-| """ let topRowHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: topRowHorizontalFormat, options: [.alignAllCenterY], metrics: metrics, views: views) allConstraints += topRowHorizontalConstraints let summaryHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-horizontalPadding-[summaryLabel]-horizontalPadding-|", metrics: metrics, views: views) allConstraints += summaryHorizontalConstraints
You're replacing the hard coded values in the format string with placeholders that represent keys from the metrics dictionary. You also set the metrics parameter to the metrics dictionary.
Auto Layout will perform string substitution, replacing the placeholder text with the value inside the metrics dictionary. In the above case, horizontalPadding will be replaced with the constant 15 points, and iconImageViewWidth will be replaced with the constant 30 points.
You've removed a repeatedly used magic number and replaced it with a nice clean variable. If you decide to change the padding, you only have to change one thing. Isn't that better? The metrics dictionary isn't limited to constants either; if you need to calculate things at run time and put them in the dictionary, that's fine too.
The final piece of the puzzle to place is how you lay out interface elements when your view controllers are embedded inside a UINavigationController or UITabBarController.
Safe Area
The UI is starting to look great, but so far you've only tried it on the traditional, rectangle-shaped screens. In September 2017, Apple introduced a new device, which doesn't quite fit this description: the iPhone X. To see how Grapevine looks on this new device, start the app in the iPhone X simulator.
Well, it isn't the most pleasant sight. While the image, and the welcome text is mostly okay, you'll notice the UI interferes with system elements on both the top and the bottom of the screen. Luckily, with the use of safe area, you can easily work around this issue!
Introduced in iOS 11, safe area indicates the area in which apps can show their UI without interfering with any special elements defined by UIKit, like the status bar, or a tab bar. In case of the iPhone X, the safe area is different in portrait, and landscape mode:
You'll notice in portrait mode, there's more space on the top, and bottom. In landscape however, the left, and right insets are larger. So far you've put all your constraint-related code in viewDidLoad(), but since the safe area may change during runtime, that'll no longer cut it. Luckily, view controllers will be notified by the viewSafeAreaInsetsDidChange() on safe area changes, so you can start there.
Open, ViewController.swift and completely remove your viewDidLoad() method. That's right, you read correctly; you'll re-implement this functionality in viewSafeAreaInsetsDidChange() later.
Next, add the following property below your IBOutlet definitions:
private var allConstraints: [NSLayoutConstraint] = []
This property will store all currently active constraints within the view controller so they can be deactivated and removed when new constraints are required.
Next, add the following implementation for viewSafeAreaInsetsDidChange():
override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() if !allConstraints.isEmpty { NSLayoutConstraint.deactivate(allConstraints) allConstraints.removeAll() } let newInsets = view.safeAreaInsets let leftMargin = newInsets.left > 0 ? newInsets.left : Metrics.padding let rightMargin = newInsets.right > 0 ? newInsets.right : Metrics.padding let topMargin = newInsets.top > 0 ? newInsets.top : Metrics.padding let bottomMargin = newInsets.bottom > 0 ? newInsets.bottom : Metrics.padding let metrics = [ "horizontalPadding": Metrics.padding, "iconImageViewWidth": Metrics.iconImageViewWidth, "topMargin": topMargin, "bottomMargin": bottomMargin, "leftMargin": leftMargin, "rightMargin": rightMargin] }
The code above will make sure you remove any previously activated constraints otherwise you'll get auto layout errors. It also extends the metrics dictionary with calculated margins. You can access the new insets by the safeAreaInsets property of the view. In case of the rectangle-shaped phones, insets will be 0; the iPhone X however will have different values based on its orientation. To cater to both of these cases, you'll use the inset value if it's greater than zero, but will fall back to the padding defined earlier if it's not.
Finally, add the following constraints using the new metrics to the end of viewSafeAreaInsetsDidChange():
let views: [String: Any] = [ "iconImageView": iconImageView, "appNameLabel": appNameLabel, "skipButton": skipButton, "appImageView": appImageView, "welcomeLabel": welcomeLabel, "summaryLabel": summaryLabel, "pageControl": pageControl] let iconVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:|-topMargin-[iconImageView(30)]", metrics: metrics, views: views) allConstraints += iconVerticalConstraints let topRowHorizontalFormat = """ H:|-leftMargin-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-rightMargin-| """ let topRowHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: topRowHorizontalFormat, options: [.alignAllCenterY], metrics: metrics, views: views) allConstraints += topRowHorizontalConstraints let summaryHorizontalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "H:|-horizontalPadding-[summaryLabel]-horizontalPadding-|", metrics: metrics, views: views) allConstraints += summaryHorizontalConstraints let iconToImageVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[iconImageView]-10-[appImageView]", metrics: nil, views: views) allConstraints += iconToImageVerticalConstraints let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[appImageView]-10-[welcomeLabel]", options: [.alignAllCenterX], metrics: nil, views: views) allConstraints += imageToWelcomeVerticalConstraints let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views) allConstraints += summaryLabelVerticalConstraints let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints( withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-bottomMargin-|", options: [.alignAllCenterX], metrics: metrics, views: views) allConstraints += summaryToPageVerticalConstraints NSLayoutConstraint.activate(allConstraints)
Build and run the project, and you'll notice the updated UI looks much better on the iPhone X:
Limitations
VFL makes it possible to write multiple constraints using just one line of code, reducing the burden on your fingertips. However, there are some limitations to the current implementation; a couple of the more notable are important to understand:
Centering of views
Using the multiplier component of constraints
Centering of Views
Within Grapevine, you've centered views using the layout options .alignAllCenterY and .alignAllCenterX.
Using these means you aligned views with other views respective to horizontal and vertical centers, however this only works if one of the views you're aligning already has enough constraints to describe its horizontal or vertical centers.
While there are tricks you can use to center views using the VFL, there are no guarantees that they'll work in future versions.
Using the Multiplier Component of Constraints
With this, you have the ability to set fixed aspect ratios on views or to do something like make a label take up only 60 percent of its superview's width. Since the VFL creates multiple constraints and returns only an array of un-named constraints, the multiplier cannot be set through the format string.
Note: You could loop through each of the constraints returned by the constraintsWithVisualFormat method, but you would have to process each of them in turn to determine the NSLayoutAttribute so that you could correctly set your multiplier. But even then, you still have to replace that constraint because the multiplier isn't mutable.
Now that you know how the Visual Format Language works, you’re ready to take this knowledge and layout your own interfaces.
You've seen how to use layout options to reduce the number of constraints you have to define. You've seen how metrics can be defined not only at compile time, but also at runtime. Lastly, you've seen that there are limitations to the Visual Format Language, but it has more pros than cons and you should take advantage of it where appropriate.
Where To Go From Here?
You can download the finished project here.
Note: Sometimes Xcode has problems when several projects share the same bundle identifier. So, if you've worked through the tutorial and try to run the downloaded final project, you might need to clean the build folder by pressing option and selecting Product \ Clean Build Folder.
For more information on the iPhone X and safe area, be sure to check out the following articles:
Update your apps for iPhone X
iPhone X - iOS Human Interface Guidelines
Positioning Content Relative to the Safe Area
I hope you enjoyed this Visual Format Language tutorial. If you have any questions or comments, please join the forum discussion below!
The post Auto Layout Visual Format Language Tutorial appeared first on Ray Wenderlich.
Auto Layout Visual Format Language Tutorial published first on http://ift.tt/2fA8nUr
0 notes