Lightweight, cross-platform code using Swift

How to turn a Mac OS app into an iOS app.


Transcript

Good afternoon! I'm going to give you a quick presentation on writing lightweight cross-platform code using Swift. And I'm actually going to start out with a very quick demo. I have a little Mac app and it's a calculator. If you're Gwendolyn Weston, you might note that this is Gwendolyn Weston's favourite colour. So, thank you Gwendolyn, you have already made my presentation more cool.

So, what would it take if we wanted to take our Mac OS app and make it into an iOS app?

00:45: This app is just a little integer calculator. It's derived from an assignment I give to some of my students, and it has buttons that you can click on. Because it's a Mac app, you can also click on buttons via the keyboard and do things there.

You can see over here in the Project Navigator, here are the files that make up our app. They probably look pretty familiar to you, even if you're an iOS person, not a Mac OS person. So, what would it take if we wanted to take our Mac OS app and make it into an iOS app?

So, let's pull up our project here, and you can see we've got our model, our assets, our ViewController, our AppDelegate, and we've got a Mac OS target. So, let's go ahead and let's add an iOS target. And let's make it a single view application, which will be fine for this app. We'll call it iOSCalculator.

Okay, so now I have a target, and you'll notice that it gave me some files here. And some of them actually look like the files that I already have.

I'm the kind of person who hates having redundant code. So, I don't really want to have another AppDelegate and another ViewController. So, I'm just going to delete those and get rid of them. Oh, and assets. I've got assets already. Why would I want another asset catalog? So, let's get rid of those.

Now, over here, I've got some other files. And if I want my iOS app to do anything, I probably should add these to my new iOS target. So, I'll add my model. I'll add my assets, and I'll also add my ViewController, and I'll add my AppDelegate. So what do you think? Is this going to work?

02:55: So, some say yes, some say no. I would say it probably wouldn't work with just what we've done, but I've got one other thing here. I've got this little OSXMagic.swift file, and I've got an iOSMagic.swift file.

So, I'm going to add the iOSMagic.swift file to my iOS target as well. And so now, I'm going to build my iOS calculator. Now we see my simulator coming up. And there's my simulator with my iOS calculator, and it works just like my Mac calculator.

Okay. So if you were curious, how do you turn your Mac app into an iOS app? That's it.

...there's pretty much nothing in either storyboard. So, all the user interface is constructed in code.

There's maybe a little bit more to it than that. So let's go back to the slides and see some more stuff. So, we have an iOS and OS X app that share the same ViewController, AppDelegate and Model.

My user interface is constructed almost entirely in Swift. So, if you're wondering how I did the magic of getting all the things that were in my main storyboard from the Mac App to the iOS app, the answer is just there is actually pretty much nothing in either storyboard. So, all the user interface is constructed in code.

Now, what did I need to do to make this work? I needed nineteen type aliases, seven extensions, four conditional compilation blocks, one function implemented separately for iOS and OS X (but only one), and one enumeration implemented for OS X. I needed the enumeration because I needed a particular enumeration that didn't exist on the OS X side but did exist on the iOS side.

So, you might notice that a lot of what I'm doing is just type aliases.


04:52: The type alias keyword allows you to define and use new names for existing types. So, here's an example of some existing types. So you can see we have classes and structures, generics, tuples, enumerations and protocols. All those things are types, so you can basically use a type alias to define a new name for that type.

Type aliases don't do everything. They only do types. Type aliases don't do modules. I really would have loved to have done a type alias for UIKit and AppKit. Then I could have just said, "Import InterfaceKit" and gotten it. But it's a module, it's not a type, so that's not going to work.

Attributes such as @UIApplicationMain or @NSApplicationMain? Unfortunately you're not going to be able to do a type alias for that. You can do type aliases for enumerations but not for individual cases in an enumeration. And you can't do type aliases for functions. So these were some things that I would have loved to have been able to use a type alias to do, but couldn't do it because they weren't types.

Now, our magic files, a lot of the magic files is just what you see here. At the top, you're seeing the iOS magic file and at the bottom, you're seeing the Mac OS magic file. And so you can see, I'm just saying instead of UIViewController, I just want to call it an XViewController. Instead of NSViewController, I want to call it an XViewController. Then if the frameworks are close enough in functionality, I can write code that can run on either iOS or on Mac OS.

Then if the frameworks are close enough in functionality, I can write code that can run on either iOS or on Mac OS.

Here is the actual ViewController. So it looks just like you would expect a ViewController to work. It's a child of XViewController, which, depending on your platform, is going to be a UIViewController or an NSViewController. You can see it's got a XLabel property and down here, I refer to the mainView as an XView. So basically, I've got a ViewController that's basically your standard ViewController that really does work for both iOS and OS X.

07:12: Now, here's one of the interesting things that you might find fascinating if you're one of the more advanced Swift users.

Here on the left side, you're seeing a demo project. This is actually a separate project I put together just for the slide. I imported UIKit, and now I've created an XButton alias for UI button. So, I'm doing that in the TypeAliasDefinitions file. Now in some other file, note that I've got a button which is an XButton and I've started typing the constructor. And one of the neat things it's doing is it's doing the auto-complete for UIButton for me.

Now what's really fascinating about this is if you look at SomeFile.swift, I didn't import UIKit. So, if a button was actually a UIButton, the compiler would be complaining right now because this file didn't import UIKit. So this file doesn't know about UIKit, but it does know about things that are internal to its module. So it does know about XButton because XButton was created in this module and Xcode is nice enough that since it knows that XButton is really a UIButton, it is giving us the auto-complete for UIButton here. So, I think Apple deserves a big round of applause for that. That's really cool.

08:46 S1: One question you might have about this, you might say, "Well, how does it know it's the UIButton right now and not the NSButton, since it could be either in this project?" And the answer is it depends on what target you're currently building for. So, if you're building for the iOS target, it will give you IOS auto-complete. If you're building for the MAC OS target, it will give you Mac OS auto-complete. So again, some really cool stuff.

The other thing that's kind of neat is that you can use your type aliases in Interface Builder.

The other thing that's kind of neat is that you can use your type aliases in Interface Builder. So here, I've got an iOS layout, and I've got a button. And I've said that the custom class for this is the TAButton that I've just defined as a type alias for UIButton. So Interface Builder will actually work with your type aliases and treat them as if they were the particular classes. So you can connect outlets in your code and do all those kinds of things. Storyboards and type aliases actually get along really well.

09:56: I couldn't do it all with type aliases. Sometimes, the two classes were different enough so they didn't have the functionality I needed. In that case, I used extensions which are ways of adding functionalities to existing types, even if you didn't write those types yourself. So here I needed to get text out of a label. The way that was working on iOS was enough different from Mac OS that I wrote a little label text extension for both platforms so that I can have a unified way of calling into them.

Conditional compilation. I didn't want to do this. I really wanted to avoid this. You can use conditional compilation to mark blocks of code that should only be included if certain conditions are met. And so, that might look like this. You can see I test and see whether I'm compiling for iOS and if so, I import the NSAttributedString module of UIKit. Otherwise, if it's Mac OS, I import the NSAttributedString module of AppKit.

Conditional code: I was trying to avoid it, but sometimes you've just got to use it.

So, a few conclusions. IOS and Mac OS have a lot in common and they get closer every year.

11:13: So, a few conclusions. IOS and Mac OS have a lot in common and they get closer every year. There are still a lot of differences.

When working on this, I went back and forth this between, "Wow, I'm amazed this worked so well." And, "Oh my gosh, I can't believe how frustrating this is." So there are still lots of stumbling blocks, but maybe not as many as you would think. Storyboards help a lot because if you've done an interface using a storyboard and then you've done the same interface in code, you know you write a lot of code if you're not using storyboards. So as it was, I needed the nineteen type aliases, seven extensions, four conditional compilation blocks, one function implemented separately for iOS and OS X, and one enumeration implemented for OS X.

Well if I'd used storyboards and not done the interface in code, it would've just been eight type aliases, four extensions, no conditional compilation blocks (which would've made me very happy), no functions implemented separately for iOS and Mac OS, and no enumerations implemented for OS X. So, storyboards actually do a lot to smooth over the differences between the platforms. 

I'm kind of new on Twitter, I'm @DevMikeEllard there, so I'd be super flattered if you followed me and maybe said something nice about my talk. If you're interested in this code, in both the before and after stages, it's up on GitHub at:

https://github.com/MichaelPatrickEllard/SwiftSummitCalculator

I'll be putting the slides up there as well. I apologise, I haven't got the purple colour in the GitHub code yet, but I promise it will be there. So that's my talk, and I look forward to your questions.