Bronze Challenge: Points of Interest

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
        }
    }

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 :slight_smile:

```
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.

Thank you for posting!