Swift is a new language. Objective C is not. Sometimes you have some Objective C code and want to make it into Swift code. I had a case of that this week when a reader asked me about continuous picker view wheels. If you are familiar with the date picker it goes from 0 to 59 and then loops back to 0, making a continuous wheel. The
UIPickerView
does not have this ability. When asked, I did not know how to do this. The reader found some code in Objective C that did that. I did a little web searching and I did find a solution. Of course it was in Objective C, not Swift.
I’ve wanted to write a piece on converting from Objective C to Swift for a while. I didn’t have a good example to use. This makes a really good example. We’ll discuss some things you should know and try an example of converting a method for a UIPickerView
from Objective C into Swift.
Header Files and Scope
Objective C uses two files for a class, unlike Swift’s single file for a class. A primary reason for this is sharing. Some data is shared in a class, some isn’t. We refer to how much something shares as its scope. Scope rules are very different between the two languages. One of those two files in Objective C is a header file or .h
file. The .h file has information that is public in scope. A public scope allows sharing. Anything private and not shared gets declared in the other file, the implementation file. Implementation files have a .m
extension. The .h
file includes properties, methods target-actions, outlets, and often protocols for delegates. It is in the header file that you find most of the class definition. The header file has no code though: it is only declarations. You will repeat all your methods and target actions in the implementation file when you write the code for it.
In the code I found the header looks like this:
#import <UIKit/UIKit.h> @interface ViewController : UIViewController<uipickerviewdatasource,uipickerviewdelegate> @property (weak, nonatomic) IBOutlet UIPickerView *myPicker; @property NSInteger maxRows; @end
In the header file, you will find a series of tags beginning with @
. You’ll find on each property a @property
tag. These will be the public properties. We have one of these as an outlet to our picker view:
@property (weak, nonatomic) IBOutlet UIPickerView *myPicker;
Let’s dissect this outlet from a Swift perspective. @property (weak, nonatomic) IBOutlet
sets an outlet with a weak variable. Since Swift has an even more automatic version of Automatic Reference Counting(ARC) than Objective C, we ignore the nonatomic
part. Next we have the class of the identifier, UIPickerView
. Finally, we have the identifier for the property *myPicker
. the asterisk (*
) means this is a pointer, which is true of most object identifiers in Objective C. Pointers in Objective C can have nil values,. It’s a good idea to their Swift equivalent an optional value.
This translates in Swift to
@IBOutlet weak var myPicker: UIPickerView!
If you never saw the difference between the two languages, you might begin to see why Swift is so speedy for coding. The outlet is merely @IBOutlet
. We have a weak variable so next we have weak var
. Next comes the identifier myPicker
with the class UIPickerView
as an optional.
Swift encourages assignment of values to variables and constants at declaration. Objective C does not. In Objective C, you declare in one place and assign in another. Often the assignment happens in the viewDidLoad
method of your .m
file. When reading Objective C code declarations, keep the viewDidLoad
handy. For example our .h
file has this line:
@property NSInteger maxRows;
in the .m
file we have this in viewDidLoad:
self.maxRows = 10;
For all properties, Objective C requires the self
identifier. These two lines equal the one line in Swift.:
let maxRows = 10
Which assigns maxRows
equal to an Int
of 10
and sets the type to Int.
Of course this also makes this a constant, which is not the case in Objective C.
There is also an @interface
tag in the .h
file. Our properties and any public methods are found between this tag and the @end
tag.
@interface ViewController : UIViewController<uipickerviewdatasource,uipickerviewdelegate> … @end
The @interface
tag tells us the identifier of this class, and its superclass. In this app it is UIViewController.
In angle brackets after the superclass is a list of adopted protocols. In our case we adopted the picker view data source and delegate. Again Swift simplifies this to essentially one line — and a bracket
class ViewController:UIViewController,UIPickerViewDataSource,UIPickerDelegate{ }
We have two @interface
tags in any Objective-C class. The second is in the .m
file, and lists private properties. For example we have this:
@interface ViewController () @property NSInteger maxElements; @end
Swift simplifies this to a mere keyword: private.
The viewDidload
method tells us to set this to 10,000. Given that information we get this:
private let maxElements = 10000
Putting that all together, we can take the header file, plus the @interface
and the viewDidLoad
in the implementation file to translate them into Swift:
import UIKit class ViewController:UIViewController,UIPickerDataSource,UIPickerDelegate{ let maxRows = 10 private let maxElements = 10000 @IBOutlet weak var myPicker:UIPickerView }
The Meat of the Matter: The Implementation File
Let’s look at the implementation file now and figure out what it is doing. I’ll assume you know about UIPickerView
and its delegates. If you don’t, read this first. The Objective C file is this:
#import "ViewController.h" @interface ViewController () @property NSInteger maxElements; @end @implementation ViewController #pragma mark delegates and data source - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ return self.maxElements; } - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ NSString *myString; NSInteger myRow = row % self.maxRows; myString = [NSString stringWithFormat:@"%li",(long)myRow]; return myString; } #pragma mark life cycle - (void)viewDidLoad { [super viewDidLoad]; self.maxElements = 10000; self.maxRows = 10; self.myPicker.delegate = self; self.myPicker.dataSource = self; [self.myPicker selectRow:self.maxElements / 2 inComponent:0 animated:NO]; } @end
If you have never worked in Objective C before, your biggest challenge is how they put stuff backwards from Swift, C++, Java or most any other language.
Objective C bases its syntax on an older language, Smalltalk. It was a Smalltalk demonstration that inspired Steve Jobs to come up with the Macintosh in the early 1980’s. Both Smalltalk and Objective C look at objects very literally. You state what your object is and then what you want to do with it. You enclose that in square brackets. For example we have
[super viewDidLoad];
which runs the superview’s viewDidLoad.
Swift writes that as a function
super.viewDidLoad()
Since this is a picker view, we need to add two data source methods to tell us about our data and one delegate method. Let’s look at one of those methods:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ return 1; }
This implements one of our two required data source methods. We have one component in our picker view, so we return 1. The minus sign at the beginning of the method declaration tells the compiler and developer that this is an instance method. If it was a plus it would be a class method. Next, the return type is in parentheses, followed by the method identifier. If there are parameters, the parameter list follows. In Swift that looks like this:
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 }
The syntax is very different between the two, but the same information is there. The next data source method works the same, returning a value for the number of rows in the picker, which in this case is a lot.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ return self.maxElements; }
We use maxElements
to set this size, which we know is 10,000 rows. Why do we need that many rows? The answer lies in the delegate method, titleForRow:
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ NSInteger myRow = row % self.maxRows; NSString *myString = [NSString stringWithFormat:@"%li",(long)myRow]; return myString; }
The declaration tells us we return a string, and have the parameters of the picker view, the row and the component. The code declares an NSInteger,
called myRow
which is the modulo of row
and maxRows.
We take this new value, convert it to a NSString,
and then return the string. Unlike properties, in methods we can declare and assign at the same time. However, we need to declare the type when we do.
What does this do? The method takes a massive set of numbers and has a row in that set of 10,000. It takes the remainder of row
divided by maxRows.
That remainder will repeat between 0 and maxrows - 1
. In effect, the picker repeats between 0 and 9 in our case. If the number for maxElements
is big enough, it is unlikely that we will run out of numbers for repetition. Since these are integers, you’ve used a small amount of memory to do this.
You can write the method like this in Swift:
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! { let myRow = row % maxRows let myString = String(format: "%i", myRow) return myString }
We returned the string as an optional. When converting from Objective-C to Swift, anything that is a pointer is best converted as an optional.
But we are not done yet. The Objective C code still has the viewDidload
method.
- (void)viewDidLoad { [super viewDidLoad]; self.maxElements = 10000; self.maxRows = 10; self.myPicker.delegate = self; self.myPicker.dataSource = self; [self.myPicker selectRow:self.maxElements / 2 inComponent:0 animated:NO]; }
The viewDidload
method does not need to assign initial values in Swift. That happens when we declare a value. Objective C requires you to explicitly say where something is with self
, while Swift is better at guessing. We can get rid of the first two assignments, and get rid of all the self
references, though you can leave them there if you wish. The last line of the method makes a call to myPicker
and selects a specific row in the picker: 5,000. This way if we roll up or down we will get a repeat. All this in Swift looks like this:
override func viewDidLoad() { super.viewDidLoad() myPicker.dataSource = self myPicker.delegate = self myPicker.selectRow(maxElements / 2, inComponent: 0, animated: false) }
Our Swift file looks like this now:
import UIKit class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate { //MARK: Properties let maxRows = 10 private let maxElements = 10000 //MARK: Outlets @IBOutlet weak var myPicker: UIPickerView! func platypus(){ //doesn't do much } //MARK: Delegates and DataSources func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 } func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return maxElements } func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! { let myRow = row % maxRows let myString = String(format: "%i", myRow) return myString } //MARK: Life cycle override func viewDidLoad() { super.viewDidLoad() myPicker.dataSource = self myPicker.delegate = self myPicker.selectRow(maxElements / 2, inComponent: 0, animated: false) platypus() } }
If you’d like to try this code out, make a new single view project. Drag a Picker View and a label out on to the storyboard. Make it look like this:
Copy the code above into the ViewController.swift file of your project.
Using the assistant editor, control-drag from the picker to the myPicker outlet. Control-drag from the I want coffee!! label to the Assistant editor. Name the new outlet statusLabel.
Build and run:
Using Text Arrays
Not everyone will want the row number. Most likely, you will want something else listed in your picker view. If you want to use text, set up an array like this:
private let coffee = ["Coffee","Latte","Mocha","Machiatto","Frappucino","Espresso","Red Eye","Black Eye","Espresso","Irish Coffee"]
Change the pickerView:titleForRow:
to this.
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! { let myRow = row % coffee.count //let myRow = row % maxRows //let myString = String(format: "%i", myRow) let myString = coffee[myRow] return myString }
We take the repeating row and use that for the index to the array. Build and run:
Returning Values
To get a value from a selected wheel, we use the modulo again. This time in the didSelectRow
delegate method. Add this to the code:
func pickerView(pickerView: UIPickerView,didSelectRow row: Int,inComponent component: Int){ let myRow = row % coffee.count let myString = coffee[myRow] statusLabel.text = "I love " + myString + "s" }
Build and run:
You can add one more line. For most simple cases it isn’t necessary if you have plenty of space to spin, but there may be times you need to limit your space, or prevent running out of space. You can add this or something like it to didSelectRow
:
pickerView.selectRow((maxElements / 2) + row, inComponent: 0, animated:false)
I’d leave maxElements
at 10000, and this means maxElements/2
is 5000. Since 5000 a number ending in 0, we can position the picker back at the middle plus offset of myRow.
Whenever the wheel stops, we are back a the middle. Note if maxElements/2
does not divide evenly to 0, you might run into problems and need a little more complicated math.
This is the basics of building a repeating picker view. It also illustrates some of the important issues you need to watch if converting between old Objective-C and Swift. These are not the only issues. Apple has an e-book on the subject of using Objective-C and Swift, and it is worth the time reading.
The Whole Objective – C Code
// // ViewController.h // InfinitePicker // // Created by Steven Lipton on 2/28/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // #import <UIKit/UIKit.h> @interface ViewController : UIViewController<uipickerviewdatasource,uipickerviewdelegate> @property (weak, nonatomic) IBOutlet UIPickerView *myPicker; @property NSInteger maxRows; @end // // ViewController.m // InfinitePicker // // Created by Steven Lipton on 2/28/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // #import "ViewController.h" @interface ViewController () @property NSInteger maxElements; @end @implementation ViewController #pragma mark delegates and data source - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ return self.maxElements; } - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ NSString *myString; NSInteger myRow = row % self.maxRows; myString = [NSString stringWithFormat:@"%li",(long)myRow]; return myString; } #pragma mark life cycle - (void)viewDidLoad { [super viewDidLoad]; self.maxElements = 10000; self.maxRows = 10; self.myPicker.delegate = self; self.myPicker.dataSource = self; [self.myPicker selectRow:self.maxElements / 2 inComponent:0 animated:NO]; } @end
The Whole Swift Code
// // ViewController.swift // // Created by Steven Lipton on 2/28/15. // Copyright (c) 2015 MakeAppPie.Com. All rights reserved. // import UIKit class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate { //MARK: Properties let maxRows = 100 private let maxElements = 10000 private let coffee = ["Coffee","Latte","Mocha","Machiatto","Frappucino","Espresso","Red Eye","Black Eye","Espresso","Irish Coffee"] //MARK: Outlets @IBOutlet weak var myPicker: UIPickerView! @IBOutlet weak var statusLabel: UILabel! //MARK: Delegates and DataSources func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 } func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return maxElements } func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! { //let myRow = row % maxRows //let myString = String(format: "%i", myRow) let myRow = row % coffee.count let myString = coffee[myRow] return myString } func pickerView(pickerView: UIPickerView,didSelectRow row: Int,inComponent component: Int){ let myRow = row % coffee.count let myString = coffee[myRow] statusLabel.text = "I love " + myString + "s" //if you want to use less elements in maxElements // or prevent ever running to the end of the list in your pickerview //you can set yourself back to the middle. It is not necessary for most cases. pickerView.selectRow((maxElements / 2) + row, inComponent: 0, animated:false) } //MARK: Life cycle override func viewDidLoad() { super.viewDidLoad() myPicker.dataSource = self myPicker.delegate = self myPicker.selectRow(maxElements / 2, inComponent: 0, animated: false) } }
Leave a Reply