Here’s my solution to the bronze challenge in this chapter (only relevant parts are included).
Here I create the UILabel and UISwitch objects, and set the autoresizing masks off because I will be using my own constraints. I also add the action-target pair for the toggling of the UISwitch and add the two new views to the MapViewController’s view
:
...
view = mapView
let label = UILabel()
label.text = "Points of Interest"
label.translatesAutoresizingMaskIntoConstraints = false
let toggle = UISwitch()
toggle.addTarget(self,
action: #selector(toggleChanged(_:)),
for: .valueChanged)
toggle.translatesAutoresizingMaskIntoConstraints = false
...
view.addSubview(segmentedControl)
view.addSubview(label)
view.addSubview(toggle)
Next, the constraints to make the UILabel and UISwitch appear in the right places. I changed the segmented control constraints’ names to have a SC prefix, so that it’s easily distinguishable:
...
SCTopConstraint.isActive = true
SCLeadingConstraint.isActive = true
SCTrailingConstraint.isActive = true
//UILabel Constraints
let LTopConstraint = label.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 15)
let LLeadingConstraint = label.leadingAnchor.constraint(equalTo: segmentedControl.leadingAnchor)
LTopConstraint.isActive = true
LLeadingConstraint.isActive = true
//UISwitch constraints
let SWTopConstraint = toggle.centerYAnchor.constraint(equalTo: label.centerYAnchor)
let SWLeadingConstraint = toggle.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10)
SWTopConstraint.isActive = true
SWLeadingConstraint.isActive = true
And finally the action method that the event above will trigger every time the UISwitch changes its value:
@objc func toggleChanged(_ toggleControl: UISwitch) {
if toggleControl.isOn {
mapView.pointOfInterestFilter = .includingAll
} else {
mapView.pointOfInterestFilter = .excludingAll
}
}
1 Like
That’s similar to what I did, but I handled the vertical constraints slightly different. Since the switch is the taller of the two components, I positioned that relative to the bottom of the segmented control, and then aligned the label to the center of the switch. That way, I don’t have to figure out the difference between the top of the label & the top of the switch and add that to the label constraint. And as long as the switch continues to be larger than the label in the future, I’m guaranteed that the switch won’t overlap the segmented control even if the size of either the switch or the label changes.
If the label font had been larger, it might have been taller than the switch, in which case I’d do it the other way around. And if the font was something that could change, you could have both sets of constraints in the code & turn the appropriate ones on or off depending on which component was larger.
Hey, would you post your code for the constraints, I’m failing to grasp the idea behind the explanation in text and I am really interested in learning how you did it. Thank you in advance!
Here’s the first version I did, where it’s a fixed set of constraints for the switch being larger than the label:
let poiSwitchTopConstraint = poiSwitch.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
let poiLabelCenterConstraint = poiLabel.centerYAnchor.constraint(equalTo: poiSwitch.centerYAnchor)
poiSwitchTopConstraint.isActive = true
poiLabelCenterConstraint.isActive = true
Then I replaced it with the following, which checks to see which one is larger:
// Vertical constraints
// Compare the height of the switch to the height of the label. Position the taller one relative to the segmented control,
// then center the smaller one on the larger.
if poiLabel.intrinsicContentSize.height < poiSwitch.intrinsicContentSize.height
{
let poiSwitchTopConstraint = poiSwitch.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
let poiLabelCenterConstraint = poiLabel.centerYAnchor.constraint(equalTo: poiSwitch.centerYAnchor)
poiSwitchTopConstraint.isActive = true
poiLabelCenterConstraint.isActive = true
}
else
{
let poiLabelTopConstraint = poiLabel.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
let poiSwitchCenterConstraint = poiSwitch.centerYAnchor.constraint(equalTo: poiLabel.centerYAnchor)
poiLabelTopConstraint.isActive = true
poiSwitchCenterConstraint.isActive = true
}
You can test this code by setting the label’s font to something fairly large.
While I thought about implementing something that would change the font size while the program is running and dynamically update the constraints, I haven’t done it yet.
1 Like
OK, I got motivated to fully implement the dynamic change code. This code will set the POI label to the default size when points of interest are turned off, and to a much larger size when they are turned on, and update the constraints on the fly:
//
// MapViewController.swift
// WorldTrotter
//
// Created by Jon Ault on 5/27/20.
// Copyright © 2020 Jon Ault. All rights reserved.
//
import UIKit
import MapKit
class MapViewController: UIViewController
{
var mapView: MKMapView!
var segmentedControl: UISegmentedControl!
var poiLabel: UILabel!
var poiSwitch: UISwitch!
var poiSwitchTopConstraint: NSLayoutConstraint!
var poiSwitchCenterConstraint: NSLayoutConstraint!
var poiLabelTopConstraint: NSLayoutConstraint!
var poiLabelCenterConstraint: NSLayoutConstraint!
override func loadView()
{
mapView = MKMapView()
view = mapView
// Map type segmented control
segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
segmentedControl.backgroundColor = UIColor.systemBackground
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self,
action: #selector(mapTypeChanged(_:)),
for: .valueChanged)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(segmentedControl)
let margins = view.layoutMarginsGuide
let topConstraint = segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
let leadingConstraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let trailingConstraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
// POI label & switch
poiLabel = UILabel()
poiLabel.text = "Points of Interest"
poiLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(poiLabel)
poiSwitch = UISwitch()
poiSwitch.addTarget(self,
action: #selector(poiSwitchChanged(_:)),
for: .valueChanged)
poiSwitch.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(poiSwitch)
// vertical constraints
poiSwitchTopConstraint = poiSwitch.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
poiLabelCenterConstraint = poiLabel.centerYAnchor.constraint(equalTo: poiSwitch.centerYAnchor)
poiLabelTopConstraint = poiLabel.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
poiSwitchCenterConstraint = poiSwitch.centerYAnchor.constraint(equalTo: poiLabel.centerYAnchor)
updateVerticalPoiConstraints()
// Horizontal constraints
// Put the label on the left side of the screen, then put the switch after the label
let poiLabelLeadingConstraint = poiLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let poiSwitchLeadingConstraint = poiSwitch.leadingAnchor.constraint(equalTo: poiLabel.trailingAnchor, constant: 8)
poiLabelLeadingConstraint.isActive = true
poiSwitchLeadingConstraint.isActive = true
}
override func viewDidLoad()
{
super.viewDidLoad()
print("MapViewController loaded its view.")
}
@objc func mapTypeChanged(_ segControl: UISegmentedControl)
{
switch segControl.selectedSegmentIndex
{
case 0: mapView.mapType = .standard
case 1: mapView.mapType = .hybrid
case 2: mapView.mapType = .satellite
default: break
}
}
@objc func poiSwitchChanged(_ poiSwitch: UISwitch)
{
if poiSwitch.isOn
{
mapView.pointOfInterestFilter = MKPointOfInterestFilter.includingAll
poiLabel.font = UIFont.systemFont(ofSize: 120)
}
else
{
mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
poiLabel.font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
}
updateVerticalPoiConstraints()
}
func updateVerticalPoiConstraints()
{
// Compare the height of the switch to the height of the label. Position the taller one relative to the segmented control,
// then center the smaller one on the larger.
if poiLabel.intrinsicContentSize.height < poiSwitch.intrinsicContentSize.height
{
poiSwitchTopConstraint.isActive = true
poiLabelCenterConstraint.isActive = true
poiLabelTopConstraint.isActive = false
poiSwitchCenterConstraint.isActive = false }
else
{
poiLabelTopConstraint.isActive = true
poiSwitchCenterConstraint.isActive = true
poiSwitchTopConstraint.isActive = false
poiLabelCenterConstraint.isActive = false
}
}
}
You can make your code look even more beautiful by inserting it between a pair of three back ticks (```), like this 
```
Insert code here
```
Here is the difference it makes.
//
// MapViewController.swift
// WorldTrotter
//
// Created by Jon Ault on 5/27/20.
// Copyright © 2020 Jon Ault. All rights reserved.
//
import UIKit
import MapKit
class MapViewController: UIViewController
{
var mapView: MKMapView!
var segmentedControl: UISegmentedControl!
var poiLabel: UILabel!
var poiSwitch: UISwitch!
var poiSwitchTopConstraint: NSLayoutConstraint!
var poiSwitchCenterConstraint: NSLayoutConstraint!
var poiLabelTopConstraint: NSLayoutConstraint!
var poiLabelCenterConstraint: NSLayoutConstraint!
override func loadView()
{
mapView = MKMapView()
view = mapView
// Map type segmented control
segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
segmentedControl.backgroundColor = UIColor.systemBackground
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self,
action: #selector(mapTypeChanged(_:)),
for: .valueChanged)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(segmentedControl)
let margins = view.layoutMarginsGuide
let topConstraint = segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
let leadingConstraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let trailingConstraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
// POI label & switch
poiLabel = UILabel()
poiLabel.text = "Points of Interest"
poiLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(poiLabel)
poiSwitch = UISwitch()
poiSwitch.addTarget(self,
action: #selector(poiSwitchChanged(_:)),
for: .valueChanged)
poiSwitch.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(poiSwitch)
// vertical constraints
poiSwitchTopConstraint = poiSwitch.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
poiLabelCenterConstraint = poiLabel.centerYAnchor.constraint(equalTo: poiSwitch.centerYAnchor)
poiLabelTopConstraint = poiLabel.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8)
poiSwitchCenterConstraint = poiSwitch.centerYAnchor.constraint(equalTo: poiLabel.centerYAnchor)
updateVerticalPoiConstraints()
// Horizontal constraints
// Put the label on the left side of the screen, then put the switch after the label
let poiLabelLeadingConstraint = poiLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let poiSwitchLeadingConstraint = poiSwitch.leadingAnchor.constraint(equalTo: poiLabel.trailingAnchor, constant: 8)
poiLabelLeadingConstraint.isActive = true
poiSwitchLeadingConstraint.isActive = true
}
override func viewDidLoad()
{
super.viewDidLoad()
print("MapViewController loaded its view.")
}
@objc func mapTypeChanged(_ segControl: UISegmentedControl)
{
switch segControl.selectedSegmentIndex
{
case 0: mapView.mapType = .standard
case 1: mapView.mapType = .hybrid
case 2: mapView.mapType = .satellite
default: break
}
}
@objc func poiSwitchChanged(_ poiSwitch: UISwitch)
{
if poiSwitch.isOn
{
mapView.pointOfInterestFilter = MKPointOfInterestFilter.includingAll
poiLabel.font = UIFont.systemFont(ofSize: 120)
}
else
{
mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
poiLabel.font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
}
updateVerticalPoiConstraints()
}
func updateVerticalPoiConstraints()
{
// Compare the height of the switch to the height of the label. Position the taller one relative to the segmented control,
// then center the smaller one on the larger.
if poiLabel.intrinsicContentSize.height < poiSwitch.intrinsicContentSize.height
{
poiSwitchTopConstraint.isActive = true
poiLabelCenterConstraint.isActive = true
poiLabelTopConstraint.isActive = false
poiSwitchCenterConstraint.isActive = false }
else
{
poiLabelTopConstraint.isActive = true
poiSwitchCenterConstraint.isActive = true
poiSwitchTopConstraint.isActive = false
poiLabelCenterConstraint.isActive = false
}
}
}
Thanks, I had forgotten about that. It would be nice if that was one of the options in the editing window.