Page 1
© 2016 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
App Frameworks #WWDC16
Session 233
Making Apps Adaptive, Part 2
David Duncan iOS Apps and FrameworksKurt Revis iOS Apps and Frameworks
Page 4
PART 1 PART 2
{}Aa
Page 6
Agenda
Basics of Sizes and Size Classes
Page 7
Agenda
Basics of Sizes and Size ClassesHow to Get the Most Out of UIKit
Page 8
Agenda
Basics of Sizes and Size ClassesHow to Get the Most Out of UIKitGoing Beyond Size Classes to Build Your Custom Experience
Page 11
IPHONE 4S
IPAD PRO 12.9”
IPHONE SE
IPHONE 6S
IPHONE 6S+
IPAD PRO 9.7”
480
576
667
736
1024
1336
320 414375 768 1024 1336
12.9” LANDSCAPE
9.7” LANDSCAPE
6S+ LANDSCAPE
6S LANDSCAPE
SE4S LANDSCAPE
9.7” SLIDE OVERPORTRAIT
9.7” SPLIT VIEW2/3 PORTRAIT
9.7” SPLIT VIEW1/2 LANDSCAPE
9.7” SPLIT VIEW2/3 LANDSCAPE
12.9” SLIDEOVER PORTRAIT
12.9” SPLIT VIEW2/3 PORTRAIT
12.9” SPLIT VIEW1/2 LANDSCAPE
12.9” SPLIT VIEW2/3 LANDSCAPE
Page 15
Compact Width Regular Width
Compact Height
Regular Height
Page 16
Your experience to makeRegular Size Class
Page 17
Views and Controls
Page 18
Views and Controls
Page 19
Views and Controls
Page 22
UISplitViewController
Page 23
UISplitViewController
Page 24
UISplitViewController
Page 25
UISplitViewController
Page 26
UISplitViewController
Page 27
UISplitViewController
Page 28
Making the most of UIKit and XcodeBest Practices
Page 30
Best Practices
Xcode tools to get your app looking great faster
Page 31
Best Practices
Xcode tools to get your app looking great faster• Interface Builder
Page 32
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
Page 33
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier
Page 34
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier• Auto Layout
Page 35
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier• Auto Layout• UITraitCollection
Page 36
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier• Auto Layout• UITraitCollection• Dynamic Type
Page 37
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier• Auto Layout• UITraitCollection• Dynamic Type• Layout Guides
Page 38
Best Practices
Xcode tools to get your app looking great faster• Interface Builder• Asset Catalogs
UIKit functionality to make it all easier• Auto Layout• UITraitCollection• Dynamic Type• Layout Guides• UIAppearance
Page 39
Images and TraitsAsset Catalogs
Page 40
Images and TraitsAsset Catalogs
Automatic image selection
Page 41
Images and TraitsAsset Catalogs
Automatic image selection
Design for Application Thinning
Page 42
Alignment InsetsAsset Catalogs
Page 43
Alignment InsetsAsset Catalogs
Page 44
Alignment InsetsAsset Catalogs
Page 45
Alignment InsetsAsset Catalogs
Page 46
Alignment InsetsAsset Catalogs
UIImage.withAlignmentRectInsets()
UIImage.alignmentRectInsets
Page 47
SlicingAsset Catalogs
Page 48
SlicingAsset Catalogs
Page 49
SlicingAsset Catalogs
UIImage.resizableImage(withCapInsets:)
UIImage.resizableImage(withCapInsets: resizingMode:)
UIImage.capInsets & UIImage.resizingMode
Page 50
SlicingAsset Catalogs
UIImage.resizableImage(withCapInsets:)
UIImage.resizableImage(withCapInsets: resizingMode:)
UIImage.capInsets & UIImage.resizingMode
Page 52
Dynamic Type
We’ve made it easier than ever to adopt
Page 53
Dynamic Type
We’ve made it easier than ever to adoptUITraitCollection.preferredContentSizeCategory
NEW
Page 54
Dynamic Type
We’ve made it easier than ever to adoptUITraitCollection.preferredContentSizeCategory
Set up your text views• .font = UIFont.preferredFont(forTextStyle:)
• .adjustsFontForContentSizeCategory = true
NEW
Page 55
Dynamic Type
We’ve made it easier than ever to adoptUITraitCollection.preferredContentSizeCategory
Set up your text views• .font = UIFont.preferredFont(forTextStyle:)
• .adjustsFontForContentSizeCategory = true
Be sure to test with all Dynamic Type sizes!
NEW
Page 56
Dynamic Type
We’ve made it easier than ever to adoptUITraitCollection.preferredContentSizeCategory
Set up your text views• .font = UIFont.preferredFont(forTextStyle:)
• .adjustsFontForContentSizeCategory = true
Be sure to test with all Dynamic Type sizes!
What's New in UICollectionView Presidio Thursday 9:00AM
NEW
Page 57
UIView.layoutMarginGuideLayout Guides
Page 58
UIView.layoutMarginGuideLayout Guides
Page 59
UIView.layoutMarginGuideLayout Guides
UIView.layoutMargins.top
.bottom
.left .right
Page 60
UIView.layoutMarginGuideLayout Guides
UIView.layoutMargins.top
.bottom
.left .right
Page 61
UIView.layoutMarginGuideLayout Guides
UIView.layoutMargins.top
.bottom
.left .rightleadingAnchor trailingAnchor
topAnchor
bottomAnchor
Page 62
UIView.readableContentGuideLayout Guides
Page 63
UIView.readableContentGuideLayout Guides
Ideal Width
Page 64
UIView.readableContentGuideLayout Guides
Ideal Width
Page 65
UIView.readableContentGuideLayout Guides
Ideal Width
Page 66
The large brown fox jumped over the lazy rabbit
UIView.readableContentGuideLayout Guides
Ideal Width
Page 67
Ideal Width
UIView.readableContentGuideLayout Guides
Page 68
The large brown fox jumped over the lazy rabbit
Ideal Width
UIView.readableContentGuideLayout Guides
Page 70
UIAppearance
Declarative
Page 71
UIAppearance
Declarative• UITabBar.appearance().unselectedTintColor = UIColor.blue
Page 72
UIAppearance
Declarative• UITabBar.appearance().unselectedTintColor = UIColor.blue
Contextual
Page 73
UIAppearance
Declarative• UITabBar.appearance().unselectedTintColor = UIColor.blue
Contextual• Trait Collections
Page 74
UIAppearance
Declarative• UITabBar.appearance().unselectedTintColor = UIColor.blue
Contextual• Trait Collections• Containment
Page 75
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 76
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 77
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 78
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
compactAppearance.setBackgroundImage(nil, for: .default)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 79
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
compactAppearance.setBackgroundImage(nil, for: .default)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 80
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
compactAppearance.setBackgroundImage(nil, for: .default)
let verticalRegularTrait = UITraitCollection(verticalSizeClass: .regular)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 81
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
compactAppearance.setBackgroundImage(nil, for: .default)
let verticalRegularTrait = UITraitCollection(verticalSizeClass: .regular)
let verticalAppearance = UINavigationBar.forTraitCollection(verticalRegularTrait)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 82
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
compactAppearance.setBackgroundImage(nil, for: .default)
let verticalRegularTrait = UITraitCollection(verticalSizeClass: .regular)
let verticalAppearance = UINavigationBar.forTraitCollection(verticalRegularTrait)
verticalAppearance.setBackgroundImage(UIImage(), for: .default)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
Page 84
Best Practices
Asset Catalogs
Page 85
Best Practices
Asset CatalogsDynamic Type
Page 86
Best Practices
Asset CatalogsDynamic TypeLayout Guides
Page 87
Best Practices
Asset CatalogsDynamic TypeLayout GuidesUIAppearance
Page 88
TakeawayThe system is going to do most of the work so you don’t have to.
Page 89
If you want to go beyond what the system provides, here’s how.
Page 90
Beyond the Basics
Page 91
Beyond the Basics
Design for all combinations of device, orientation, and size
Page 92
Beyond the Basics
Design for all combinations of device, orientation, and sizeImplement each design and change between them
Page 93
Beyond the Basics
Design for all combinations of device, orientation, and sizeImplement each design and change between themUse reusable elements
Page 94
My Incredibly Adaptive App
Page 95
My Incredibly Adaptive App
Item Title Description
1 A Lorem ipsum dolor sit er elit lamet…
2 B Ut enim ad minim veniam, quis nostrud…
3 C Duis aute irure dolor in reprehenderit…
Page 97
A
B
C
Design 1: “Tall”
Page 98
A
B
C
A B C
Design 1: “Tall” Design 2: “Wide”
Page 99
A
B
C
A B C
Width < Height
Design 1: “Tall” Design 2: “Wide”
Page 100
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Page 101
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Page 102
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Page 103
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Page 104
Design your app for different adaptations
Page 105
Design your app for different adaptations
Consider the possible combinations of device, orientation, and size
Page 106
Design your app for different adaptations
Consider the possible combinations of device, orientation, and sizeMake designs
Page 107
Design your app for different adaptations
Consider the possible combinations of device, orientation, and sizeMake designsDefine rules for which design to use
Page 109
Defining Rules
Check if size exactly matches
Page 110
Defining Rules
Check if size exactly matches
Page 111
Defining Rules
Define Boolean conditions
Check if size exactly matches
Page 112
Defining Rules
Define Boolean conditions• Use size class
Check if size exactly matches
Page 113
Defining Rules
Define Boolean conditions• Use size class• Compare value to a threshold
Check if size exactly matches
Page 114
Defining Rules
Define Boolean conditions• Use size class• Compare value to a threshold • Compare values to each other
Check if size exactly matches
Page 117
Implement the Designs
Page 118
Implement the Designs
Find the size of the app
Page 119
Implement the Designs
Find the size of the appUse the rules to decide which design to use
Page 120
Implement the Designs
Find the size of the appUse the rules to decide which design to useApply the design to the UI
Page 121
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Where Does the Code Go?
Page 122
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Where Does the Code Go?
Page 123
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Where Does the Code Go?
• View is loaded on demand
Page 124
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Where Does the Code Go?
• View is loaded on demand• View is not yet in a superview, and layout has not yet happened
Page 125
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Use init(), loadView(), and viewDidLoad() only for code that is the same across designs
Where Does the Code Go?
• View is loaded on demand• View is not yet in a superview, and layout has not yet happened
Page 126
override func viewWillLayoutSubviews() {
// Add code here
}
Where Does the Code Go?
Page 127
override func viewWillLayoutSubviews() {
// Add code here
}
Where Does the Code Go?
Page 128
override func viewWillLayoutSubviews() {
// Add code here
}
• View is in a superview, and layout of superviews has happened
Where Does the Code Go?
Page 129
override func viewWillLayoutSubviews() {
// Add code here
}
• View is in a superview, and layout of superviews has happened• Good time to manipulate things inside your view controller
Where Does the Code Go?
Page 130
Be Careful with Layout
override func viewWillLayoutSubviews() {
// Add code here
}
Page 131
Be Careful with Layout
override func viewWillLayoutSubviews() {
// Add code here
}!
Page 132
Be Careful with Layout
override func viewWillLayoutSubviews() {
// Add code here
}
• Do as little work as possible
!
Page 133
Be Careful with Layout
override func viewWillLayoutSubviews() {
// Add code here
}
• Do as little work as possible• Find out what has changed since last time
!
Page 134
Be Careful with Layout
override func viewWillLayoutSubviews() {
// Add code here
}
• Do as little work as possible• Find out what has changed since last time• Don’t cause a layout loop
!
Page 135
Be Careful with Layout
What's New in Auto Layout Presidio Friday 3:00PM
override func viewWillLayoutSubviews() {
// Add code here
}
• Do as little work as possible• Find out what has changed since last time• Don’t cause a layout loop
!
Page 136
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Page 137
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Each item is a view
Page 138
A
B
C
A B C
Width < Height Width >= Height
Design 1: “Tall” Design 2: “Wide”
Each item is a viewUse UIStackView for horizontal and vertical arrangement
Page 139
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 140
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 141
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 142
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 143
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 144
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 145
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
Page 151
“Can you make it pop?”
Page 152
“Can you make it pop?”
During rotation, make the items grow
Page 153
“Can you make it pop?”
During rotation, make the items growAfter rotation, go back to normal
Page 154
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition:
{ _ in
stackView.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
},
completion:
{ _ in
UIView.animate(withDuration: 0.5, animations: {
stackView.transform = CGAffineTransform.identity
})
}
)
}
Page 155
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition:
{ _ in
stackView.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
},
completion:
{ _ in
UIView.animate(withDuration: 0.5, animations: {
stackView.transform = CGAffineTransform.identity
})
}
)
}
Page 156
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition:
{ _ in
stackView.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
},
completion:
{ _ in
UIView.animate(withDuration: 0.5, animations: {
stackView.transform = CGAffineTransform.identity
})
}
)
}
Page 157
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition:
{ _ in
stackView.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
},
completion:
{ _ in
UIView.animate(withDuration: 0.5, animations: {
stackView.transform = CGAffineTransform.identity
})
}
)
}
Page 158
override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition:
{ _ in
stackView.transform = CGAffineTransform(scaleX: 1.4, y: 1.4)
},
completion:
{ _ in
UIView.animate(withDuration: 0.5, animations: {
stackView.transform = CGAffineTransform.identity
})
}
)
}
Page 164
Reusable Elements
Page 165
Reusable Elements
Build your app out of pieces that can be reused in different designs
Page 166
Reusable Elements
Build your app out of pieces that can be reused in different designs
Each piece is typically a view controller
Page 167
Reusable Elements
Build your app out of pieces that can be reused in different designs
Each piece is typically a view controller• View tree and constraints
Page 168
Reusable Elements
Build your app out of pieces that can be reused in different designs
Each piece is typically a view controller• View tree and constraints• Connections to other view controllers
Page 169
Reusable Elements
Build your app out of pieces that can be reused in different designs
Each piece is typically a view controller• View tree and constraints• Connections to other view controllers• Connections to the rest of your app
Page 170
View Controller Roles
Page 171
View Controller Roles
Container View Controller
Page 172
View Controller Roles
Contained View Controller
Container View Controller
Page 173
A
B
C
A B C
Design 1: “Tall” Design 2: “Wide”
Page 174
ExampleContainerViewController
A
B
C
A B C
Design 1: “Tall” Design 2: “Wide”
Page 175
ExampleContainerViewController
ElementViewController
ElementViewController
ElementViewController
A
B
C
A B C
Design 1: “Tall” Design 2: “Wide”
Page 177
A
B
C
A…………………..
……………….……..
Tap preview to present
Page 178
A
B
C
A…………………..
……………….……..
Tap preview to present
Tap detail to dismiss
Page 179
A
B
C
A…………………..
……………….……..
Tap preview to present
Tap detail to dismiss
ExampleContainerViewController
Page 180
A
B
C
A…………………..
……………….……..
Tap preview to present
Tap detail to dismiss
ExampleContainerViewController
SmallElementViewController
Page 181
A
B
C
A…………………..
……………….……..
Tap preview to present
Tap detail to dismiss
ExampleContainerViewController
SmallElementViewController LargeElementViewController
Page 182
A…………………..
B……….
……
C……….
……
Page 183
A…………………..
B……….
……
C……….
……
ExampleContainerViewController
LargeElementViewController
Page 184
Contained View Controllers
Page 185
Contained View Controllers
Page 186
Contained View Controllers
Page 187
Contained View Controllers
Page 188
Contained View Controllers
Page 189
Contained View Controllers
Page 190
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 191
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 192
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 193
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 194
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 195
class SmallElementViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: "largeElement")
present(newElementViewController, animated: true, completion: nil)
}
}
}
Page 196
class LargeElementViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isBeingPresented() {
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
dismiss(animated: true, completion: nil)
}
}
}
Page 197
class LargeElementViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isBeingPresented() {
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
dismiss(animated: true, completion: nil)
}
}
}
Page 198
class LargeElementViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isBeingPresented() {
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
dismiss(animated: true, completion: nil)
}
}
}
Page 199
class LargeElementViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isBeingPresented() {
let tapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(self.tapped))
view.addGestureRecognizer(tapGestureRecognizer)
}
}
func tapped(_ gestureRecognizer: UITapGestureRecognizer) {
if gestureRecognizer.state == .ended {
dismiss(animated: true, completion: nil)
}
}
}
Page 201
Design Object
Stores information describing a design
Page 202
Design Object
Stores information describing a designImmutable struct for safety
Page 203
Design Object
Stores information describing a designImmutable struct for safetyAllows comparison
Page 204
struct Design {
let axis: UILayoutConstraintAxis // .vertical or .horizontal
enum ElementKind {
case small
case large
}
let elementKind: ElementKind
var elementIdentifier: String {
switch elementKind {
case .small: return "smallElement"
case .large: return "largeElement"
}
}
}
Page 205
struct Design {
let axis: UILayoutConstraintAxis // .vertical or .horizontal
enum ElementKind {
case small
case large
}
let elementKind: ElementKind
var elementIdentifier: String {
switch elementKind {
case .small: return "smallElement"
case .large: return "largeElement"
}
}
}
Page 206
struct Design {
let axis: UILayoutConstraintAxis // .vertical or .horizontal
enum ElementKind {
case small
case large
}
let elementKind: ElementKind
var elementIdentifier: String {
switch elementKind {
case .small: return "smallElement"
case .large: return "largeElement"
}
}
}
Page 207
struct Design {
let axis: UILayoutConstraintAxis // .vertical or .horizontal
enum ElementKind {
case small
case large
}
let elementKind: ElementKind
var elementIdentifier: String {
switch elementKind {
case .small: return "smallElement"
case .large: return "largeElement"
}
}
}
Page 208
extension Design: Equatable { }
func == (left: Design, right: Design) -> Bool {
return left.axis == right.axis && left.elementKind == right.elementKind
}
Page 209
extension Design: Equatable { }
func == (left: Design, right: Design) -> Bool {
return left.axis == right.axis && left.elementKind == right.elementKind
}
Page 210
Container View Controller
Page 211
Container View Controller
Has three child view controllers
Page 212
Container View Controller
Has three child view controllersUses rules to decide a design
Page 213
Container View Controller
Has three child view controllersUses rules to decide a designUpdates at each layout
Page 214
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 215
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 216
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 217
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 218
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 219
class ExampleContainerViewController: UIViewController {
var elementViewControllers: [UIViewController?] = [nil, nil, nil]
var displayedDesign: Design? = nil
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let newDesign = decideDesign(size)
if displayedDesign != newDesign {
applyDesign(newDesign)
displayedDesign = newDesign
}
}
Page 220
func decideDesign(_ size: CGSize) -> Design {
let axis: UILayoutConstraintAxis
if size.width > size.height {
axis = .horizontal
} else {
axis = .vertical
}
let elementKind: Design.ElementKind
let widthThreshold = CGFloat(750)
if size.width < widthThreshold {
elementKind = .small
} else {
elementKind = .large
}
return Design(axis: axis, elementKind: elementKind)
}
Page 221
func decideDesign(_ size: CGSize) -> Design {
let axis: UILayoutConstraintAxis
if size.width > size.height {
axis = .horizontal
} else {
axis = .vertical
}
let elementKind: Design.ElementKind
let widthThreshold = CGFloat(750)
if size.width < widthThreshold {
elementKind = .small
} else {
elementKind = .large
}
return Design(axis: axis, elementKind: elementKind)
}
Page 222
func decideDesign(_ size: CGSize) -> Design {
let axis: UILayoutConstraintAxis
if size.width > size.height {
axis = .horizontal
} else {
axis = .vertical
}
let elementKind: Design.ElementKind
let widthThreshold = CGFloat(750)
if size.width < widthThreshold {
elementKind = .small
} else {
elementKind = .large
}
return Design(axis: axis, elementKind: elementKind)
}
Page 223
func decideDesign(_ size: CGSize) -> Design {
let axis: UILayoutConstraintAxis
if size.width > size.height {
axis = .horizontal
} else {
axis = .vertical
}
let elementKind: Design.ElementKind
let widthThreshold = CGFloat(750)
if size.width < widthThreshold {
elementKind = .small
} else {
elementKind = .large
}
return Design(axis: axis, elementKind: elementKind)
}
Page 224
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 225
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 226
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 227
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 228
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 229
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 230
func applyDesign(_ newDesign: Design) {
stackView.axis = newDesign.axis
if displayedDesign?.elementKind != newDesign.elementKind {
for (index, elementViewController) in elementViewControllers.enumerated() {
if let oldElementViewController = elementViewController {
removeOldElementViewController(oldElementViewController)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newElementViewController = storyboard.instantiateViewController(
withIdentifier: newDesign.elementIdentifier)
addNewElementViewController(newElementViewController)
elementViewControllers[index] = newElementViewController
}
}
}
Page 231
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 232
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 233
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 234
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 235
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 236
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 237
// Helper functions to be a well-behaved container view controller:
func addNewElementViewController(_ elementViewController: UIViewController)
{
addChildViewController(elementViewController)
stackView.addArrangedSubview(elementViewController.view)
elementViewController.didMove(toParentViewController: self)
}
func removeOldElementViewController(_ elementViewController: UIViewController)
{
elementViewController.willMove(toParentViewController: nil)
elementViewController.view.removeFromSuperview()
elementViewController.removeFromParentViewController()
}
Page 246
Beyond the Basics
Page 247
Beyond the Basics
Design for all combinations of device, orientation, and size
Page 248
Beyond the Basics
Design for all combinations of device, orientation, and sizeImplement each design and change between them
Page 249
Beyond the Basics
Design for all combinations of device, orientation, and sizeImplement each design and change between themUse reusable elements
Page 250
PART 1 PART 2
{}Aa
Page 251
More Information
https://developer.apple.com/wwdc16/233
Page 252
Related Sessions
Making Apps Adaptive, Part 1 Presidio Wednesday 11:00AM
Inclusive App Design Pacific Heights Tuesday 10:00AM
What’s New in UICollectionView in iOS 10 Presidio Thursday 9:00AM
What's New in Auto Layout Presidio Friday 3:00PM
Building Adaptive Apps with UIKit WWDC 2014
Adopting Multitasking in iOS 9 WWDC 2015
Page 253
Labs
Cocoa Touch and 3D Touch Lab Frameworks Lab C Friday 10:30AM