I decided to implement a UISegmentedControl to switch from “Interesting” to “Recent” Flickr photos.
It builds successfully, but when I ran the project just to see the segmented control, it crashed.
The log doesn’t display much but I uncovered this error:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeeb8afc98)
I simply grafted the code from WorldTrotter into PhotosViewController.swift:
override func loadView() {
let interestingString = NSLocalizedString("Interesting", comment: "Interesting photos")
let recentString = NSLocalizedString("Recent", comment: "Most recent photos")
let segmentedControl = UISegmentedControl(items: [interestingString, recentString])
// runtime error points me here, but without showing anything explicit in the console log
segmentedControl.backgroundColor = UIColor.systemBackground
segmentedControl.selectedSegmentIndex = 0
// segmentedControl.addTarget(self, action: #selector(mapTypeChanged(_:)), for: .valueChanged) not ready to implement this yet...unless this is what's crashing it. But I suppose here is where I toggle between my "interesting" and "recent" functions
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(segmentedControl)
let topConstraint = segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
// using the layout margins of the photo view
let margins = view.layoutMarginsGuide
let leadingConstraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let trailingConstraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
// activating the programmatic layout constraints
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
}
What could cause the crash? And have others considered other ways to implement
switching between the different types of photo sets?
It looks to me like when you try to add the segmented control to the view, it’s causing loadView to get called again, and you end up in an infinite recursion of loadView calls. I’m not sure why. It might be that the navigation control is conflicting with the segmented control in some way. In WorldTrotter, the segmented control was added to the map view; here it’s being added to the default top view, which in this case might be the navigation controller.
You might need to add the segmented control to the Photorama scene using the storyboard rather than doing it programmatically.
I chose to do this by adding a button to the navigation controller.
Alright, I went with a Segmented Control:
However, while I could pull up Recent photos after replacing the function from the book with a custom function I adapted for Flickr’s recent photos, the segmented control doesn’t load a recent image when I switch to it.
FlickrAPI.swift:
enum EndPoint: String {
case interestingPhotos = "flickr.interestingness.getList"
case recentPhotos = "flickr.photos.getRecent"
}
struct FlickrAPI {
// Silver challenge - expose a URL for recent photos
static var recentPhotosURL: URL {
return flickrURL(endPoint: .recentPhotos, parameters: ["extras": "url_z,date_taken"])
}
}
PhotosViewController.swift:
@IBOutlet weak var segmentedControl: UISegmentedControl! // Silver challenge
override func viewDidLoad() {
super.viewDidLoad()
...
// Silver challenge
switch segmentedControl.selectedSegmentIndex {
case 0:
// fetch interesting photos
store.fetchInterestingPhotos {
(photosResult) in
switch photosResult {
case let .success(photos):
print("Successfully found \(photos.count) photos.")
// listing 20.34 p 414 - show the first photo
if let firstInterestingPhoto = photos.first {
self.updateImageView(for: firstInterestingPhoto)
}
case let .failure(error):
print("Error fetching interesting photos: \(error)")
}
}
default:
// fetch recent photos
store.fetchRecentPhotos {
(photosResult) in
switch photosResult {
case let .success(photos):
print("Successfully found \(photos.count) photos.")
// listing 20.34 p 414 - show the first photo
if let firstRecentPhoto = photos.first {
self.updateImageView(for: firstRecentPhoto)
}
case let .failure(error):
print("Error fetching interesting photos: \(error)")
}
}
}
}
Do I have to do something programmatically to refresh the UIImageView? Or did I forget to do something else?
Yes. If you go back & look at WorldTrotter, there was a mapTypeChanged function, and in loadView that function was connected to the segmented control. Every time the segmented control selection changed, the function would get called to update the map to the selected type.
You’ll need to do something similar here & provide a function that the segmented control can call which will display the selected photograph type.
Okay, I got it to work the way I wanted. Thanks again, Jon.
However, I’ll need to return to this and refactor it because I’m repeating store.fetchInterestingPhotos so that an interesting photo appears when I first load the app:
PhotosViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// listing 20.15 p401 - initiating the web service request
store.fetchInterestingPhotos {
(photosResult) in
switch photosResult {
case let .success(photos):
print("Successfully found \(photos.count) photos.")
// listing 20.34 p 414 - show the first photo
if let firstInterestingPhoto = photos.first {
self.updateImageView(for: firstInterestingPhoto)
}
case let .failure(error):
print("Error fetching interesting photos: \(error)")
}
}
// Silver challenge
segmentedControl.addTarget(self, action: #selector(photoTypeChanged(_:)), for: .valueChanged)
}
...
// for Silver Challenge:
@objc func photoTypeChanged(_ segControl: UISegmentedControl) {
switch segControl.selectedSegmentIndex {
case 0:
// fetch interesting photos
store.fetchInterestingPhotos {
(photosResult) in
switch photosResult {
case let .success(photos):
print("Successfully found \(photos.count) photos.")
// listing 20.34 p 414 - show the first photo
if let firstInterestingPhoto = photos.first {
self.updateImageView(for: firstInterestingPhoto)
}
case let .failure(error):
print("Error fetching interesting photos: \(error)")
}
}
default:
// fetch recent photos
store.fetchRecentPhotos {
(photosResult) in
switch photosResult {
case let .success(photos):
print("Successfully found \(photos.count) photos.")
// listing 20.34 p 414 - show the first photo
if let firstRecentPhoto = photos.first {
self.updateImageView(for: firstRecentPhoto)
}
case let .failure(error):
print("Error fetching interesting photos: \(error)")
}
}
}
}