NSConstraints crashes app after second load of UIView - ios

I've got a custom UIView that I instantiate in a view controller with this function, displayedTimer is an iVar of the view controller:
func changeViewModeTo(mode: String){
if mode == "settings" {
addSettingsModeConstraints()
animatedLayoutIfNeeded(removeView: true)
}
if mode == "timer" {
displayedTimer = TimerView.init()
displayedTimer.frame = CGRect(x: (self.view.bounds.size.width)/2 - 50, y: (self.view.bounds.size.height)/2 - 80, width: 100, height: 160)
let colors = timer.getColorScheme()
displayedTimer.setColorScheme(colorLight: colors["lightColor"]!, colorDark: colors["darkColor"]!)
displayedTimer.setTimeRemainingLabel(timer.duration)
displayedTimer.setCountDownBarFromPercentage(1.0)
displayedTimer.layer.zPosition = 100 //make sure the timer view sits on top of the settings panel
displayedTimer.timerLabel.hidden = false
displayedTimer.translatesAutoresizingMaskIntoConstraints = false
let pinchGestureRecogniser = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchDetected(_:)))
displayedTimer.addGestureRecognizer(pinchGestureRecogniser)
self.view.addSubview(displayedTimer)
addTimerModeConstraints()
animatedLayoutIfNeeded(removeView: false)
}
}
If the mode is set to timer then it creates a subclass of UIView and sets an instance variable to it, constraints are added to make it full screen and then an animated layoutIfNeeded() is called. If the mode being set is settings then it deactivates the timerConstraints, adds new constraints to shrink the view, calls an animated layoutIfNeeded and then removes the view from the superView.
func animatedLayoutIfNeeded(removeView removeView: Bool){
UIView.animateWithDuration(0.2, delay: 0, options: [UIViewAnimationOptions.CurveEaseIn] , animations: {
self.view.layoutIfNeeded()
}) { (true) in
if removeView == true {
self.displayedTimer.removeFromSuperview()
}
}
}
The constraints are added and removed with these methods (settingsConstraints and timerConstraints are iVars of the view controller):
//MARK: - Layout Constraints
func addSettingsModeConstraints() {
let views = ["timerView": displayedTimer]
let timerHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-75-[timerView]-75-|",
options: [],
metrics: nil,
views: views)
settingsConstraints += timerHorizontalConstraints
let timerVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-105-[timerView]-85-|",
options: [],
metrics: nil,
views: views)
settingsConstraints += timerVerticalConstraints
NSLayoutConstraint.deactivateConstraints(timerConstraints)
NSLayoutConstraint.activateConstraints(settingsConstraints)
}
func addTimerModeConstraints() {
let views = ["timerView": displayedTimer]
let timerHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-0-[timerView]-0-|",
options: [],
metrics: nil,
views: views)
timerConstraints += timerHorizontalConstraints
let timerVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-0-[timerView]-0-|",
options: [],
metrics: nil,
views: views)
timerConstraints += timerVerticalConstraints
NSLayoutConstraint.activateConstraints(timerConstraints)
}
changeViewModeTo is called from a pinch gesture recogniser (negative pinch sets one mode, positive pinch sets another mode).
The first time I pinch, the view is created and goes full screen. I then reverse pinch and the view shrinks and is removed. Then when I pinch again to start the process over the app crashes, there are no console errors but there is a red error over the line of code: NSLayoutConstraint.activateConstraints(timerConstraints)
I'm guessing removing the subview has caused the reference to NSConstraints to disappear?
Any thoughts would be great as I can't figure it out.

So turns out this is a simple fix, call removeAll() on settingsConstraints and timerConstraints before recreating them and activating them solves the problem.

Related

Animate Visual Format Language Constraint

I am trying to animate a button from the left side of a container to the right side. The position of this button is defined by the Visual Format Language. (The project is 100% code, no Storyboard issues possible)
These are my lines for the left side (working):
messageInputContainerView.addSubview(moreButton)
messageInputContainerView.addConstraintsWithFormat(format: "H:|-3-[v0(35)]-8-[v1][v2(60)]|", views: moreButton, ..., ...)
messageInputContainerView.addConstraintsWithFormat(format: "V:|[v0]|", views: moreButton)
These for the right side + animation (not working):
//I have read that I first have to remove the old constraints in order to apply new ones
moreButton.removeConstraints(moreButton.constraints)
//The only difference here is that the pipe ('|') is on the right side and that I don't care about the two other objects in the container
messageInputContainerView.addConstraintsWithFormat(format: "H:[v0(35)]-3-|", views: moreButton)
messageInputContainerView.addConstraintsWithFormat(format: "V:|[v0]|", views: moreButton)
...
//These lines should be fine, but I include them nontheless
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut, animations: {
self.moreButton.setTitle("⤇", for: .normal)
self.messageInputContainerView.layoutIfNeeded()
})
If someone is wondering, the 'addConstraintsWithFormat' function looks like this:
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...){
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated(){
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
I am new to VFL in general, therefore I would really appreciate a helpful answer.
Here is an image of the container and the moreButton. (both at the very bottom)
Your problem is likely that your constraints are not added to the correct UIViews.
You don't have to worry about adding constraints to views if you activate the constraints instead by calling NSLayoutConstraint(activate:) with the VFL constraints:
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...){
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated(){
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
And then there is this line:
//I have read that I first have to remove the old constraints in order to apply new ones
moreButton.removeConstraints(moreButton.constraints)
Again, you should be deactivating constraints (setting their isActive property to false) instead of removing constraints. The problem with removing constraints like this is that some of the constraints on moreButton are stored in the constraints array of moreButton's superview.
Keep the constraints you want to deactivate in the future in an array in your VC and then pass the constraints to NSLayoutConstraint.deactivate() or set the isActive property to false for those constraints you wish to deactivate.
Update:
Your button isn't animating correctly. That is because it is still contrained to the textField. To animate your moreButton, you need to free it of other constraints that are restricting its position.
First, I would change addConstraintsWithFormat(format:views:) to return the constraints it created as [NSLayoutConstraint]. This will allow you to deactivate them later.
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) -> [NSLayoutConstraint] {
var viewsDictionary = [String: UIView]()
let constraints: [NSLayoutConstraint]
for (index, view) in views.enumerated(){
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
constraints = NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary)
NSLayoutConstraint.activate(constraints)
return constraints
}
Add a property to your viewController:
var moreButtonConstraints = [NSLayoutConstraint]()
Then break up the format "H:|-3-[v0(35)]-8-[v1][v2(60)]|" into "H:|-3-[v0(35)]" and "H:|-46-[v1][v2(60)]|" and activate them separately. That will separate the constraints for your moreButton from the constraints for the other views, allowing you to move the moreButton without affecting the other views.
messageInputContainerView.addSubview(moreButton)
// save these constraints in a property to be deactivated later
moreButtonConstraints = messageInputContainerView.addConstraintsWithFormat(format: "H:|-3-[v0(35)]", views: moreButton)
// we don't need to keep these constraints, so assign them to _
_ = messageInputContainerView.addConstraintsWithFormat(format: "H:|-46-[v1][v2(60)]|", views: ..., ...)
_ = messageInputContainerView.addConstraintsWithFormat(format: "V:|[v0]|", views: moreButton)
Then, when it is time to animate:
// deactivate the old constraints
NSLayoutConstraint.deactivate(moreButtonConstraints)
// add constraints to move the moreButton to the right side
_ = messageInputContainerView.addConstraintsWithFormat(format: "H:[v0(35)]-3-|", views: moreButton)

How do I constrain UIView to UICollectionViewCell

Opposite of what everyone else seems to be asking. I have a collectionView with cells into which I am adding a loaded .xib as a subview. Easy enough.
However, the size of the collectionView cells change at run-time based on different criteria so I need the subview to be properly constrained to the size of the collectionViewCell's contentView. In an attempt to do this, I have this code when I add the subview:
/**
Used to present a view in the collectionView. WidetView is a subclass of UIView
*/
class WidgetCell: UICollectionViewCell, Reusable {
var widgetView: WidgetView! {
didSet {
for view in contentView.subviews {
view.removeFromSuperview()
}
contentView.addSubview(widgetView)
let views = ["view": widgetView as Any]
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: views)
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: views))
contentView.addConstraints(constraints)
}
}
}
Unfortunately, what's presented isn't a set of views that are properly constrained. The added UIView is the same size as what's defined in the .xib.
How can I fix this? If you want a sample project (Swift 3) look here: https://github.com/AaronBratcher/FitToCell
It's a simple case of calling contentView.activateConstraints(constraints) with your existing code and then at the point where the cells are shown, cell.layoutIfNeeded(). This forces the cell to recalculate the bounds of it's subviews.
Since the expected size of the cell is known for layout purposes, pass that size to the WidgetCell instance and then set the view's frame size.
final class WidgetCell: UICollectionViewCell {
var cellSize: CGSize?
var widgetView: WidgetView! {
didSet {
for view in contentView.subviews {
view.removeFromSuperview()
}
let frame: CGRect
if let cellSize = cellSize {
frame = CGRect(origin: CGPoint(x: 0, y: 0), size: cellSize)
} else {
frame = contentView.frame
}
widgetView.frame = frame
widgetView.setNeedsLayout()
contentView.addSubview(widgetView)
}
}
}

Programmatically adding a view between a tableView and bottomLayoutGuide

I have a UIViewController with a UITableView that fills the screen for an ad-supported version. MyViewController is embedded in a UINavigationController and it has a UITabBarController at the bottom.
There will be 2 versions of this app:
1) Paid - I have this configured on a storyboard. It works as desired.
2) Ad Supported - Try as I may, I can't get the banner to draw in the right spot. I'm trying to do this:
topLayoutGuide
tableview
standard height padding
bannerView (50 height)
standard height padding
bottomLayoutGuide
Instead, the bannerView is being drawn on top of the tableView, rather than between the tableView and the bottomLayoutGuide
I call a method I created called configureBannerView from viewDidLoad. Here' the relevant portion of the code that lays out the view in Visual Format Language:
var allConstraints = [NSLayoutConstraint]()
let horizontalTableViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[tableView]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalTableViewConstraint
let horizontalBannerViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[leftBannerViewSpacer]-[bannerView(320)]-[rightBannerViewSpacer(==leftBannerViewSpacer)]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalBannerViewConstraint
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide][tableView]-[leftBannerViewSpacer(50)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += verticalConstraints
I can't figure out why this isn't working. Below is the complete configureBannerView method.
func configureBannerView() {
if adSupported == false {
// Do nothing, leave it alone
} else {
// remove existing constraints
tableView.removeConstraints(tableView.constraints)
tableView.translatesAutoresizingMaskIntoConstraints = false
// create dictionary of views
var views: [String : AnyObject] = [
"tableView" : tableView,
"topLayoutGuide": topLayoutGuide,
"bottomLayoutGuide": bottomLayoutGuide]
// Create a frame for the banner
let bannerFrame = CGRect(x: 0, y: 0, width: kGADAdSizeBanner.size.width, height: kGADAdSizeBanner.size.height)
// Instnatiate the banner in the frame you just created
bannerView = GADBannerView.init(frame: bannerFrame)
bannerView?.translatesAutoresizingMaskIntoConstraints = false
// add the bannerView to the view
view.addSubview(bannerView!)
// add bannerView to the view dictionary
views["bannerView"] = bannerView
// Create spacers for left and right sides of bannerView
// 32.0 = leftSpacer left pad + leftSpacer right pad + rightSpacer left pad + rightSpacer right pad
// Calculate width of spacer
let spacerWidth = (screenSize.width - kGADAdSizeBanner.size.width - 32.0) / 2
// Instantiate left and right pads
// 50.0 = height of bannerView
let leftBannerViewSpacer = UIView(frame: CGRect(x: 0, y: 0, width: spacerWidth, height: 50.0))
let rightBannerViewSpacer = UIView(frame: CGRect(x: 0, y: 0, width: spacerWidth, height: 50.0))
leftBannerViewSpacer.translatesAutoresizingMaskIntoConstraints = false
rightBannerViewSpacer.translatesAutoresizingMaskIntoConstraints = false
// add the spacers to the subview
view.addSubview(leftBannerViewSpacer)
view.addSubview(rightBannerViewSpacer)
// add to the views dictionary
views["leftBannerViewSpacer"] = leftBannerViewSpacer
views["rightBannerViewSpacer"] = rightBannerViewSpacer
// Create metric for tabBarHeight
let tabBarHeight = tabBarController?.tabBar.frame.height
// Create a dictionary of metrics
let metrics: [String : CGFloat] = ["tabBarHeight": tabBarHeight!]
var allConstraints = [NSLayoutConstraint]()
let horizontalTableViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[tableView]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalTableViewConstraint
let horizontalBannerViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[leftBannerViewSpacer]-[bannerView(320)]-[rightBannerViewSpacer(==leftBannerViewSpacer)]|",
options: [NSLayoutFormatOptions.AlignAllCenterY],
metrics: nil,
views: views)
allConstraints += horizontalBannerViewConstraint
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide][tableView]-[leftBannerViewSpacer(50)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += verticalConstraints
NSLayoutConstraint.activateConstraints(allConstraints)
Thank you for reading. I welcome suggestions to resolve my erroneous code.
Storyboard solution
Do not add constraints programmatically unless all other avenues have failed: there is no way to see what you are doing until you build, link, run.
A much simpler solution requiring much less code is to hold on to references of your views an constraints from Interface Builder, and:
constraintToTweak.constant = newValue
Unlike the other properties, the constant may be modified after constraint creation. Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
or
constraintToTweak.active = false
The receiver may be activated or deactivated by manipulating this property.  Only active constraints affect the calculated layout.  Attempting to activate a constraint whose items have no common ancestor will cause an exception to be thrown. Defaults to NO for newly created constraints.
With ishaq's and SwiftArchitect's assistance, I figured it out. It turns out, the key to getting the GADBannerView to display properly below a UITableView without adding it as a footer was super simple. I ended up chopping 100 lines of needless code by doing the following:
1) UIStackView: If you haven't used this before, stop what you're doing now and follow this tutorial.
I added my tableView: UITableView and the bannerView: GADBannerView in interface builder to a vertical UIStackView
2) I created IBOutlets (I had tableView already) for both of them on MyViewController.
3) My refactored configureBannerView looks like this.
// I added these properties at the top. I did not know you could drag
// constraints from Interface Builder onto the ViewController
#IBOutlet weak var bannerViewHeight: NSLayoutConstraint!
#IBOutlet weak var bannerViewWidth: NSLayoutConstraint!
func configureBannerView() {
if adSupported == true {
loadAd() // follow Google's documentation to configure your requests
} else {
bannerView.hidden = true // this hides the bannerView if it's not needed
// Removes constraint errors when device is rotated
bannerViewWidth.constant = 0.0
bannerViewHeight.constant = 0.0
}
}
Thanks to ishaq & SwiftArchitect for pointing me in the right direction.
Add a new adMob UIViewController as root controller and addSubview of the old root controller.
I had a programmatic very similar problem case having to add adMob to a UITabBarController root controller with tab Controllers that were UINavigationController. Them all making hard resistance in trying to resize them internally, the ads were just typed over the application views. I might have just not been lucky enough finding a working way that path. Read a lot Stackoverflow and Google hints.
I also believe Apples and Googles recommendations are like having the ads below and kind of separate from but tight to the app. The Android Admob banners appears the same way, and same behavior is wanted. (Did the same project for Android recently).
Make a new app root controller, a ViewController with Admob
To be reusable and smoothly implementable in future projects, about a new adUnitID is all needed to be put in (and the name of the old root controller). The banner will be below the applications screen.
Just add this class file to the project and make it the root controller in the AppDelegate.swift file.
import UIKit
import GoogleMobileAds
class AdsViewController: UIViewController, GADBannerViewDelegate {
let adsTest = true
let ads = true //false //
let adUnitIDTest = "ca-app-pub-3940256099942544/2934735716"
let adUnitID = "ca-app-pub-xxxxxxxxxxxxxxxx/xxxxxxxxxx"
let tabVc = MainTabBarController()
var bannerView: GADBannerView!
override func viewDidLoad() {
super.viewDidLoad()
// Add the adMob banner
addBanner(viewMaster: view)
// Add the old root controller as a sub-view
view.addSubview(tabVc.view)
// Make its constraints to fit the adMob
if(ads) {
tabVc.view.translatesAutoresizingMaskIntoConstraints = false
tabVc.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tabVc.view.bottomAnchor.constraint(equalTo: bannerView.topAnchor).isActive = true
tabVc.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tabVc.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// To be notified of rotation
NotificationCenter.default.addObserver(self, selector: #selector(AdsViewController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
}
/*******************************
Ads banner standard engine
*******************************/
func addBanner(viewMaster: UIView) {
if(ads) {
// We instantiate the banner with desired ad size.
bannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
addBannerViewToView(bannerView, viewMaster: viewMaster)
if(adsTest) {
bannerView.adUnitID = adUnitIDTest
} else {
bannerView.adUnitID = adUnitID
}
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
}
}
func addBannerViewToView(_ bannerView: GADBannerView, viewMaster: UIView) {
bannerView.translatesAutoresizingMaskIntoConstraints = false
viewMaster.addSubview(bannerView)
viewMaster.addConstraints(
[NSLayoutConstraint(item: bannerView,
attribute: .bottom,
relatedBy: .equal,
toItem: viewMaster.safeAreaLayoutGuide,
attribute: .bottom,
multiplier: 1,
constant: 0),
NSLayoutConstraint(item: bannerView,
attribute: .centerX,
relatedBy: .equal,
toItem: viewMaster,
attribute: .centerX,
multiplier: 1,
constant: 0)
])
}
/*******************************
Rotation (change SmartBanner)
*******************************/
#objc func rotated() {
if UIDevice.current.orientation.isPortrait {
bannerView.adSize = kGADAdSizeSmartBannerPortrait
}
if UIDevice.current.orientation.isLandscape {
bannerView.adSize = kGADAdSizeSmartBannerLandscape
}
}
}

IOS bug constraints ViewDidAppear vs ViewDidLoad

I work since 2 days on this problem without understanding the mindfuck behind it...
So to explain quickly, I create a view based on a Nib in an other storyboard like this:
let storyboard = UIStoryboard(name: "TestNib", bundle: nil)
testNib = storyboard.instantiateViewControllerWithIdentifier("TestNib") as! TestNib
testNib.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(testNib.view)
and I add some constraints manually with constraintsWithVisualFormat like this:
constraint_V = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-(10)-[conversationMenu(300)]",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil, views: ["conversationMenu": testNib.view])
let constraint_H = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[conversationMenu]|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil, views: ["conversationMenu": testNib.view])
self.view.addConstraints(constraint_V)
self.view.addConstraints(constraint_H)
No problem, everything work super fine. (I also try with a view in the storyboard by the way).
The main problem is when I want to animate this view, the constraints into "testNib" (self.view.addSubview(testNib.view)) break. And this happened when I create the constraintsWithVisualFormat in the ViewDidAppear. In the ViewDidLoad everything work perfectly...
This are my instances' variables:
var testNib: TestNib!
var constraint_V: [NSLayoutConstraint]!
var completion = false
And this is my code to animate the constraints:
if completion == false {
for constraint in constraint_V {
if constraint.firstAttribute == NSLayoutAttribute.Top {
constraint.constant = -200
}
}
} else {
for constraint in constraint_V {
if constraint.firstAttribute == NSLayoutAttribute.Top {
constraint.constant = 10
}
}
}
completion = !completion
UIView.animateWithDuration(1, animations: {
self.view.layoutIfNeeded()
}, completion: {(value: Bool) in
})
You will ask me, why if it work in the ViewDidLoad you simply do not use it?
It's because my main problem is when I "presentViewController" and dismiss it, constraints break again and the only way I found to reproduce this bug is to insert my initialization into the ViewDidAppear.
I have commit and push my code into a github if you want to test with me: https://github.com/Comanga/Bug-Constraints.
What you need to see is the green view that is not stuck anymore to the top constraint.
Oh you read this until this line? Thank you <3
iOS9 - SWIFT 2.0
Hope this would help you
viewDidAppear is called finally after UIKit laid out sub views and displayed them.
So it is not working and you can think another way to re-layout your view controller after added/removed constraints.
try setNeedsUpdateConstraints or something

A view height is 568 on 3.5-inch screen

I need to create a view with a scroll view and a page control in it, and place 7 views inside scroll view.
To lay out subviews inside the scroll view I use pure Auto layout Approach, that is described here.
So I have my controller with XIB file (I don't use storyboards here) that is pretty simple: it's a UIScrollView and UIPageControl with all constraints set up.
And I have a XIB for a UIView subclass Slide which has 2 UIImageViews and 1 UILabel, and there's also some constraints.
To add some views to UIScrollView I use this code in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
var pSlide: Slide?
for var i = 0; i < 7; i++ {
var slide = Slide(frame: self.view.bounds, imageName: "slide-\(i+1)-bg", text: NSLocalizedString("slides_\(i+1)", comment: ""))
slide.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.addSubview(slide)
var dict: [NSObject : AnyObject] = ["currentSlide" : slide]
if let previousSlide = pSlide {
dict["previousSlide"] = previousSlide
let constraintsHorizontal = NSLayoutConstraint.constraintsWithVisualFormat("H:[previousSlide][currentSlide]", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsHorizontal)
let constraintsVertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsVertical)
} else {
let constraintsVertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsVertical)
let constraintsLeft = NSLayoutConstraint.constraintsWithVisualFormat("H:|[currentSlide]", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsLeft)
}
if i == 6 {
let constraintsRight = NSLayoutConstraint.constraintsWithVisualFormat("H:[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsRight)
}
pSlide = slide
}
pageControl.numberOfPages = numberOfSlides
view.layoutSubviews()
}
In this piece of code I create a Slide instance, and set all necessary constraints to it, according to pure Auto Layout approach.
init() method of the Slide class looks like this:
init(frame: CGRect, imageName: String, text: String) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("Slide", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = frame
self.layoutIfNeeded()
println("Frame is \(frame); view.frame is \(self.view.frame)")
backgroundImage.image = UIImage(named: imageName)
textLabel.text = text
}
I hoped that
self.view.frame = frame
self.layoutIfNeeded()
will help me but no. The problem is, on 3.5 inch screen all my UIScrollView subviews have the height of 568, which is the normal height for 4 inch display, but not for 3.5 inch.
I'm checking the height in viewDidAppear(animated:) method. But, in init() method of Slide class the height appears to be ok — 480.
I'm trying to solve it for second day already, and still nothing works. I know that this may be much more simple to implement without using Auto Layout and Interface Builder, but I need to do it with these.
I used UIPageViewController instead of all this mess, and it works just fine.

Resources