Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when programatically adding constraints to views in Swift

Tags:

I'm getting errors in the log that tell me there are conflicting constraints that can't be simultaneously satisfied when programatically adding constraints to views in Swift, yet the UI is looking the way I intended it to when running the simulator as you can see in the screenshot. The leading edge of the left button is aligned with the leading edge of the segmented control, the trailing edge of the right button is aligned with the trailing edge of the segmented control.

I believe it's these constraints causing problems, as commenting those 2 out is what's stopping the errors from being thrown, but then the UI doesn't look as I intended.

Could someone please help me understand what I've done wrong?

Conversion View Controller loaded
2016-07-29 22:51:34.555 WorldTrotter[800:41503] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
"<NSLayoutConstraint:0x7fd836313550 UIButton:0x7fd836312e60'Current Location'.left == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.left>",
"<NSLayoutConstraint:0x7fd836313660 UIButton:0x7fd836312e60'Current Location'.trailing == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.centerX - 8>",
"<NSLayoutConstraint:0x7fd836318800 'UIView-Encapsulated-Layout-Width' H:[MKMapView:0x7fd83351d900(0)]>",
"<NSLayoutConstraint:0x7fd836312e00 'UIView-leftMargin-guide-constraint' H:|-(0)-[UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'](LTR)   (Names: '|':MKMapView:0x7fd83351d900 )>",
"<NSLayoutConstraint:0x7fd833794d30 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide']-(0)-|(LTR)   (Names: '|':MKMapView:0x7fd83351d900 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fd836313660 UIButton:0x7fd836312e60'Current Location'.trailing == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.centerX - 8>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
2016-07-29 22:51:34.556 WorldTrotter[800:41503] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
"<NSLayoutConstraint:0x7fd836313ee0 UIButton:0x7fd8363136b0'Next Pin'.leading == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.centerX + 8>",
"<NSLayoutConstraint:0x7fd836313fb0 UIButton:0x7fd8363136b0'Next Pin'.trailing == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.trailing>",
"<NSLayoutConstraint:0x7fd836318800 'UIView-Encapsulated-Layout-Width' H:[MKMapView:0x7fd83351d900(0)]>",
"<NSLayoutConstraint:0x7fd836312e00 'UIView-leftMargin-guide-constraint' H:|-(0)-[UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'](LTR)   (Names: '|':MKMapView:0x7fd83351d900 )>",
"<NSLayoutConstraint:0x7fd833794d30 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide']-(0)-|(LTR)   (Names: '|':MKMapView:0x7fd83351d900 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fd836313fb0 UIButton:0x7fd8363136b0'Next Pin'.trailing == UILayoutGuide:0x7fd83630fc20'UIViewLayoutMarginsGuide'.trailing>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

import UIKit
import MapKit

class MapViewController: UIViewController, MKMapViewDelegate {

var mapView: MKMapView!
let locationManager = CLLocationManager()
var coordinates: [CLLocationCoordinate2D] = []
var counter: Int = 0

override func loadView() {
    mapView = MKMapView()
    mapView.delegate = self
    view = mapView


    //Adding pins to map
    let firstLocation = CLLocationCoordinate2DMake(5.000000, -5.000000)
    let secondLocation = CLLocationCoordinate2DMake(-5.000000, 5.000000)
    coordinates.append(firstLocation)
    coordinates.append(secondLocation)



    let segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
    segmentedControl.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.5)
    segmentedControl.selectedSegmentIndex = 0
    segmentedControl.translatesAutoresizingMaskIntoConstraints = false

    segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged(_:)), forControlEvents: .ValueChanged)

    view.addSubview(segmentedControl)

    let margins = view.layoutMarginsGuide

    let topConstraint = segmentedControl.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 8)

    let leadingConstraint = segmentedControl.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor)


    let trailingConstraint = segmentedControl.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
    topConstraint.active = true
    leadingConstraint.active = true
    trailingConstraint.active = true

    let userButton = UIButton()
    view.addSubview(userButton)
    userButton.translatesAutoresizingMaskIntoConstraints = false
    userButton.addTarget(self, action: #selector(MapViewController.userButtonSelected(_:)), forControlEvents: UIControlEvents.TouchUpInside)
    userButton.setTitle("Current Location", forState: .Normal)
    userButton.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.7)


    let bTopConstraint = userButton.topAnchor.constraintEqualToAnchor(segmentedControl.bottomAnchor, constant: 8)

    //Problematic constraint I think
    let bLConstraint = userButton.leftAnchor.constraintEqualToAnchor(margins.leftAnchor)

    let bTConstraint = userButton.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -8)
    bTopConstraint.active = true
    bLConstraint.active = true
    bTConstraint.active = true

    let pinsButton = UIButton()
    view.addSubview(pinsButton)
    pinsButton.translatesAutoresizingMaskIntoConstraints = false
    pinsButton.addTarget(self, action: #selector(MapViewController.pinsButtonSelected(_:)), forControlEvents: UIControlEvents.TouchUpInside)
    pinsButton.setTitle("Next Pin", forState: .Normal)
    pinsButton.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.7)

    let pTopConstraint = pinsButton.topAnchor.constraintEqualToAnchor(segmentedControl.bottomAnchor, constant: 8)

    //Problematic constraint I think
    let pLConstraint = pinsButton.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: 8)

    let pTConstraint = pinsButton.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
    pTopConstraint.active = true
    pLConstraint.active = true
    pTConstraint.active = true

}

func mapTypeChanged(segControl: UISegmentedControl) {
    switch segControl.selectedSegmentIndex {
    case 0:
        mapView.mapType = .Standard
    case 1:
        mapView.mapType = .Hybrid
    case 2:
        mapView.mapType = .Satellite
    default:
        break
    }

}

override func viewDidLoad() {
    super.viewDidLoad()
}

func userButtonSelected(button: UIButton) {
    if mapView.showsUserLocation == false {
        mapView.showsUserLocation = true
    } else {
        mapView.showsUserLocation = false
    }
}

func pinsButtonSelected(button: UIButton) {
    if counter >= coordinates.count {
        counter = 0
    }
    let dropPin = MKPointAnnotation()
    dropPin.coordinate = coordinates[counter]
    counter += 1
    mapView.addAnnotation(dropPin)
    mapView.setCenterCoordinate(dropPin.coordinate, animated: false)
}

func mapViewWillStartLocatingUser(mapView: MKMapView) {
    // Ask for Authorisation from the User.
    self.locationManager.requestAlwaysAuthorization()

    // For use in foreground
    self.locationManager.requestWhenInUseAuthorization()
    if CLLocationManager.locationServicesEnabled() {
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()
    }

    mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true)
    print("Tracking user")
}

}


Screenshot of the layout, even with constraint errors in log

like image 883
sineil Avatar asked Jul 29 '16 13:07

sineil


People also ask

Can we add constraints to view?

You cannot add a check constraint to a materialized view. You can “roll it by hand” by creating a table and using TRUNCATE and INSERT ... SELECT to refresh the data.

What is translatesAutoresizingMaskIntoConstraints?

translatesAutoresizingMaskIntoConstraints. A Boolean value that determines whether the view's autoresizing mask is translated into Auto Layout constraints.

How do you add constraints in UIKit?

To create a constraint between two views, Control-click one of the views and drag to the other. When you release the mouse, Interface Builder displays a HUD menu with a list of possible constraints.


1 Answers

Your problem is coming from the fact that you haven't given your MKMapView a proper frame. When you create it like this:

mapView = MKMapView()

You are setting the frame to 0 width and 0 height. Auto Layout is then translating that frame into constraints for the width and height of the view.

One of the conflicting constraints listed is:

<NSLayoutConstraint:0x7fd836318800 'UIView-Encapsulated-Layout-Width' H:[MKMapView:0x7fd83351d900(0)]>

The 0 in [MKMapView:0x7fd83351d900(0)] indicates that there is a constraint to make the width of MKMapView to be 0, which of course is not what you want.

You can fix this by giving your map view a proper frame when you create it:

Replace:

mapView = MKMapView()

with:

mapView = MKMapView(frame: UIScreen.mainScreen().bounds)

My answers below worked around this problem by letting iOS set up the view, which it does correctly.


Previous Answer

I was not able to reproduce your problem initially, but when I put your viewController in a UITabBarController, I too saw the Auto Layout error messages.

To make it work, I used a standard UIViewController in the Storyboard and moved your loadView code into viewDidLoad. I added the MKMapView as a subview of self.view along with appropriate constraints to make it the same size as self.view:

override func viewDidLoad() {
    super.viewDidLoad()
    
    mapView = MKMapView()
    mapView.delegate = self
    mapView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(mapView)
    
    NSLayoutConstraint.activateConstraints([
        mapView.topAnchor.constraintEqualToAnchor(view.topAnchor),
        mapView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
        mapView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
        mapView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
    ])
    
    //Adding pins to map
    let firstLocation = CLLocationCoordinate2DMake(5.000000, -5.000000)
    let secondLocation = CLLocationCoordinate2DMake(-5.000000, 5.000000)
    coordinates.append(firstLocation)
    coordinates.append(secondLocation)
    
    let segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
    segmentedControl.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.5)
    segmentedControl.selectedSegmentIndex = 0
    segmentedControl.translatesAutoresizingMaskIntoConstraints = false
    
    segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged(_:)), forControlEvents: .ValueChanged)
    
    view.addSubview(segmentedControl)
    
    let margins = view.layoutMarginsGuide
    
    let topConstraint = segmentedControl.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 8)
    
    let leadingConstraint = segmentedControl.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor)
    
    
    let trailingConstraint = segmentedControl.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
    topConstraint.active = true
    leadingConstraint.active = true
    trailingConstraint.active = true
    
    let userButton = UIButton()
    view.addSubview(userButton)
    userButton.translatesAutoresizingMaskIntoConstraints = false
    userButton.addTarget(self, action: #selector(MapViewController.userButtonSelected(_:)), forControlEvents: UIControlEvents.TouchUpInside)
    userButton.setTitle("Current Location", forState: .Normal)
    userButton.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.7)
    
    
    let bTopConstraint = userButton.topAnchor.constraintEqualToAnchor(segmentedControl.bottomAnchor, constant: 8)
    
    //Problematic constraint I think
    let bLConstraint = userButton.leftAnchor.constraintEqualToAnchor(margins.leftAnchor)
    
    let bTConstraint = userButton.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -8)
    bTopConstraint.active = true
    bLConstraint.active = true
    bTConstraint.active = true
    
    let pinsButton = UIButton()
    view.addSubview(pinsButton)
    pinsButton.translatesAutoresizingMaskIntoConstraints = false
    pinsButton.addTarget(self, action: #selector(MapViewController.pinsButtonSelected(_:)), forControlEvents: UIControlEvents.TouchUpInside)
    pinsButton.setTitle("Next Pin", forState: .Normal)
    pinsButton.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.7)
    
    let pTopConstraint = pinsButton.topAnchor.constraintEqualToAnchor(segmentedControl.bottomAnchor, constant: 8)
    
    //Problematic constraint I think
    let pLConstraint = pinsButton.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: 8)
    
    let pTConstraint = pinsButton.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
    pTopConstraint.active = true
    pLConstraint.active = true
    pTConstraint.active = true
    
}

Alternate solution:

  1. Move your loadView code to viewDidLoad and get rid of these lines:

    mapView = MKMapView()
    view = mapView
    
  2. In the Storyboard, change the class of the view in the ViewController to MKMapView.

  3. Make the mapView an @IBOutlet:

     @IBOutlet var mapView: MKMapView!
    
  4. Wire up the outlet in the Storyboard.

  5. Set the MKMapView delegate. Control-drag from mapView in Storyboard to the ViewController icon at the top of the viewController and select delegate from the pop-up. You could also hook up the delegate by calling:

    mapView.delegate = self
    

    in viewDidLoad.

like image 174
vacawama Avatar answered Oct 04 '22 17:10

vacawama