Problem with the code on Page No 105 (Programmatic Controls)


#1

Hi Guys,
Here is the code listed in Page 105 (of the print edition and 300 in iBooks edition) the code is given
as

segmentedControl.addTarget(self, action: "mapTypeChanged:", forControlEvents: .ValueChanged)

Now as I was typing this I did not put colon after “mapTypeChanged” as its given in double quotes so I took it for string literal but not doing this caused my program to crash (though it built successfully and ran succesfully and crashed only after I clicked on one of the three tabs :“Standard”, “Hybrid” and “Satellite”). So it looks like “mapTypeChanged:” is not string literal then can anyone please explain what is that and why missing colon(:slight_smile: leads to program crash.


#2

That is a Selector (created from a string). Selectors uniquely reference a method to call. The selector you passed is “mapTypeChanged:” which expects a method with the name “mapTypeChanged” that takes a single argument (inferred from the “:”). So the method that goes with the “mapTypeChanged:” selector would be:

func mapTypeChanged(sender: AnyObject) { ... }

Two other examples. If you had just “mapTypeChanged” without the “:” it would expect a method named “mapTypeChanged” that takes in no arguments. So you’d have:

func mapTypeChanged() { ... }

Finally, you might have a method with multiple arguments, such as this method on array:

func insert(newElement: Element, atIndex i: Int) { ... }

The selector for this method would be “insert:atIndex:”. The syntax for selectors looks a little weird because it was create with Objective-C (not Swift, in fact decades before Swift came out). A rule of thumb is “the number of colons you see in a method declaration will be mirrored in a selector declaration.” The method:

func mapTypeChanged(sender: AnyObject) { ... }

has one colon in the function, so it’ll have one colon in the selector.

Here are two links to Apple’s developer site with some information about selectors, but feel free to ask any questions here for clarification.
developer.apple.com/library/mac … ector.html
developer.apple.com/library/ios … CAPIs.html


#3

So here’s my code for the map view, done programmatically according to the book 5th edition:

import UIKit
import MapKit

class MapViewController: UIViewController {
    
    var mapView: MKMapView!
    
    override func loadView() {
        
        mapView = MKMapView()
        view = mapView
        
        let segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
        segmentedControl.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.5)
        segmentedControl.selectedSegmentIndex = 0
        segmentedControl.addTarget(self, action: "mapTypeChanged:", forControlEvents: .ValueChanged)
        
        func mapTypeChanged(segControl: UISegmentedControl) {
            switch segControl.selectedSegmentIndex {
            case 0:
                mapView.mapType = .Standard
            case 1:
                mapView.mapType = .Hybrid
            case 2:
                mapView.mapType = .Satellite
            default:
                break
            }
        }
        
        segmentedControl.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(segmentedControl)
        
        let topConstraint = segmentedControl.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 8)
        
        let margins = view.layoutMarginsGuide
        let leadingConstraint = segmentedControl.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor)
        let trailingConstraint = segmentedControl.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor)
        
        topConstraint.active = true
        leadingConstraint.active = true
        trailingConstraint.active = true
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("MapViewController loaded its view")
    }
    
    
}

I have double checked this code against what the book has and it appears to be the same without any errors or syntax issues reported by Xcode, yet, when I go to click the Satellite or Hybrid button on the map, the app crashes.

Here is a copy of the crash message:

ConversionViewController loaded its view
MapViewController loaded its view
2016-02-18 22:10:16.164 WorldTrotter[35532:1316140] -[WorldTrotter.MapViewController mapTypeChanged:]: unrecognized selector sent to instance 0x7fd752d3b160
2016-02-18 22:10:16.199 WorldTrotter[35532:1316140] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[WorldTrotter.MapViewController mapTypeChanged:]: unrecognized selector sent to instance 0x7fd752d3b160’
*** First throw call stack:
(
0 CoreFoundation 0x0000000105effe65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x0000000107c3fdeb objc_exception_throw + 48
2 CoreFoundation 0x0000000105f0848d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x0000000105e5590a forwarding + 970
4 CoreFoundation 0x0000000105e554b8 _CF_forwarding_prep_0 + 120
5 UIKit 0x000000010671e194 -[UIApplication sendAction:to:from:forEvent:] + 92
6 UIKit 0x000000010688d6fc -[UIControl sendAction:to:forEvent:] + 67
7 UIKit 0x000000010688d9c8 -[UIControl _sendActionsForEvents:withEvent:] + 311
8 UIKit 0x000000010693afe5 -[UISegmentedControl _setSelectedSegmentIndex:notify:animate:] + 690
9 UIKit 0x000000010693d6b9 -[UISegmentedControl touchesEnded:withEvent:] + 232
10 UIKit 0x000000010678d49b -[UIWindow _sendTouchesForEvent:] + 835
11 UIKit 0x000000010678e1d0 -[UIWindow sendEvent:] + 865
12 UIKit 0x000000010673cb66 -[UIApplication sendEvent:] + 263
13 UIKit 0x0000000106716d97 _UIApplicationHandleEventQueue + 6844
14 CoreFoundation 0x0000000105e2ba31 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 17
15 CoreFoundation 0x0000000105e2195c __CFRunLoopDoSources0 + 556
16 CoreFoundation 0x0000000105e20e13 __CFRunLoopRun + 867
17 CoreFoundation 0x0000000105e20828 CFRunLoopRunSpecific + 488
18 GraphicsServices 0x000000010a7b3ad2 GSEventRunModal + 161
19 UIKit 0x000000010671c610 UIApplicationMain + 171
20 WorldTrotter 0x0000000105a59aed main + 109
21 libdyld.dylib 0x000000010b08892d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

I went online to investigate this error regarding selectors, and I heard about the colon issue as you mentioned. However, it appears I am implementing that here in my code. Some potential fixes were to make sure everything was linked correctly with the @IBActions, but I that shouldn’t matter here since the mapview was done programmatically.

I would appreciate any help you can provide. Also, the app runs fine and everything functions and displays as expected, the only time the app crashes is at the time of clicking the map buttons to display whether its Hybrid or Satellite.


#4

[color=#FF0000]Problem[/color]: mapTypeChanged: defined as a nested function.

Please pay heed to 7stud7stud’s post: viewtopic.php?f=624&t=11158&p=31965#p31955.


#5

@ Homushi

You shouldn’t wirte the “func mapTypeChanged(segControl: UISegmentedControl)…” in the loadView(). You should take it out of the loadView(). Then the issue is solved.


#6

I’m having the same problem and still nothing has worked.


#8

Cgerton1,

I’m having the same problem…

There were a couple of different problems mentioned in this thread, but both were the result of an inability to look at code and copy it, so it’s not clear how you think we should help you do that.


#9

if you still have issues with the code, try to change "mapTypeChanged:" to #selector(mapTypeChanged(_:))


#10

if you still have issues with the code, try to change “mapTypeChanged:” to #selector(mapTypeChanged(_:))

I wanted to upvote this, then realized I wasn’t in reddit lol. XCode 7.3 was nice enough to point this out after the first crash now and offer to fix it directly. Pretty cool.


#11

The code shown in the book is listed as obsolete by Xcode.

segmentedControl.addTarget(self, action: "mapTypeChanged:", forControlEvents: .ValueChanged)

should be replaced by

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

Xcode 7.3.1 suggests the fix, and the fix does work.


#12

In xcode version 8.0, this does not seem to be working properly.
Type ‘MapViewController’ has no member ‘mapTypeChanged’


#13

In Xcode 8.0, it works:
segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged), for: .valueChanged)


#14

thank you so much rongchao_zhang!! was having the same problem as OP, and this was the suggestion that did the trick for me in XCode 8.0


#15

@rongchao_zhang this was the fix I was looking for as well


#16

I saw someone use this pattern, it’s good to me

private extension Selector {
static let mapTypeChanged =
#selector(MapViewController.mapTypeChanged)
}

segmentedControl.addTarget(self, action: .mapTypeChanged, for: .valueChanged)


#17

Thank you @rongchao_zhang, works for me in Xcode 8.1


#18

@rongchao_zhang thanks, works for me in XCode 8.2.1


#19

Awesome. This one gave me such a head…ache


#21

I am using xcode 8.2.1 and the only code working is

segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged(_:)), for: .valueChanged)

inside of class MapViewController and func mapTypeChanged placing outside of MapViewController.


#22

Thanks rongchao_zhang, it works in Xcode 8.3.3