When you write an iOS application you typically import the Foundation
or UIKit
framework and think nothing of it. You might `import Foundation` if you want to work with Strings, Dates, the file system, or threads, while you might import UIKit
if you want to use UITableViewController
or UIAlertController
. If you’re importing UIKit
then you can forget Foundation
altogether, since UIKit
does it behind the scenes.
The point is that these frameworks define all the methods and functions and classes that you can access in your code. You’re going to have trouble connecting to a custom Bluetooth device if you forget to import CoreBluetooth
. Even more, you’re definitely going to have trouble getting that indie SpriteKit game off the ground if you don’t import SpriteKit
.
Each of these frameworks are built by Apple. They are available to developers so we can take advantage of the technologies that make iOS apps successful.
But what is a framework and why do we use them?
A framework is a modular and reusable set of code that is used as the building blocks of higher-level pieces of software.
The best reason to use frameworks is that they can be built once and be reused an infinite number of times!
Let’s take a look at UIKit
again; UIKit
is developed by Apple and is constantly being updated. It is a project that can be reused repeatedly by developers to build new applications. We can use it to define awesome functionality in our projects like animation, (UI)Buttons, transitions, and (UI)Colors.
We know that Apple has developed and uses frameworks extensively on the iOS platform, but what about us?
Creating frameworks is a relatively easy task, and using them can be just as easy as let greenColor = UIColor.black
. Let’s dive into making custom Cocoa Touch Frameworks!
Creating Frameworks
We will build a framework to mimic the custom status alerts that Apple has been using across iOS for a couple years now. I have typically noticed them in the Music app, the Podcast app, and the News app.
These custom status alerts have started popping up all over the place, yet there isn’t a public framework provided by Apple to take advantage of them!
These alerts are relatively simple, overall. They consist of a UIVisualEffectView (with a blur)
(as the background), an UIImage
, and some text. How hard can it be to create a framework to generate these? It’s actually not that bad!
Getting Started
Let’s open Xcode and create a new project. Select the iOS tab, scroll down to Framework & Library
and select Cocoa Touch Framework
.
I’m going to name it `AOModalStatus`, where the `AO` stands for my initials.
You’ll notice this pattern across GitHub where people upload their frameworks for the rest of the developer community to make good use of.
Apple’s frameworks follow the two-letter prefix too. Consider the UI in
UIKit
and the GL inGLKit
; the former stands for User Interface, while the latter references OpenGL (or OpenGL ES), which the Open Graphics Library thatGLKit
is built upon.
For this tutorial, select Swift as your language. Save the project wherever you’d like, and let’s get started!
Start by creating a new file. Under Source
, select Cocoa Touch Class
and then select UIView
from the drop down. I’m naming it AOModalStatusView
.
Let’s add another file. This time under User Interface
select View
and name it AOModalStatusView.xib
.
A XIB file acts a lot like a storyboard, except that it is meant for a single view instead of a set of them.
Open the XIB file and delete the view that is currently there. In the Object Library search for Visual Effects View with Blur
and drag it into the XIB. In the Size Inspector select Alignment Rectangle
from the drop down and enter 230 points in the width and height.
Now just like with a storyboard, we’re going to add some objects to the view that will comprise our custom `AOModalStatusView`.
Drag an Image View
and two Label
s to the visual effects view and place them as such:
I set the image to have a width and height of 130 points. The image is centered horizontally and is 10 points from the top of the visual effects view. The first label is positioned 20 points below that and the second label is positioned just 5 points lower. Both labels have leading and trailing constraints to the make them reach each side of the view. For the second label set the Lines
option in the Attributes Inspector to 2, because it is a subheading that may take up some multiple lines.
That was a mouthful, but once you have the constraints set up, the hard part is finished!
Since a XIB is a single view in a file, you should assign classes to the `File’s Owner` section. Storyboards contain multiple views so we don’t do the same thing there.
Select File’s Owner
(1) in the Document Outline of the XIB. Go to the Identity Inspector (2) and assign the Class
section to AOModalStatusView
(3).
Now we are ready to start mixing the XIB with code!
We’ll start by making some outlets for the most important objects in the view, the image and the two labels. Select the Assistant Editor so that you can view the .xib and .swift files simultaneously.
Add a basic class like so:
class AOModalStatusView: UIView {
}
Just as you would in a storyboard, control+click and drag each element inside of the class that we created a few minutes ago. It should look a little something like this:
If you have trouble adding the outlets, build the project (command+B) and try again. If that doesn’t work make sure that you set the
File Owner’s
class to “AOModalStatusView” and not the view’s class.
Now we can go back to the standard editor and forget about the XIB file for a bit. Open AOModalStatusView.swift.
Since we will be creating public functions to edit these properties, we want to hide access to the raw outlets. Add the keyword private
right before the keyword weak
for each of the outlets like such:
Next, add the following code after the outlets:
// MARK: Set Up Viewpublic override init(frame: CGRect) {
// For use in code
super.init(frame: frame)
setUpView()
}
public required init?(coder aDecoder: NSCoder) {
// For use in Interface Builder
super.init(coder: aDecoder)
setUpView()
}
These two initializers are what Xcode starts with when it will create a new AOModalStatusView
. The first one can be run directly from code while the second one is needed for working in Interface Builder.
You may have noticed that the setUpView()
function is causing trouble…primarily because we haven’t created it yet.
But before we add func setUpView()
we need to add some supporting variables. Add the following to the class just below the outlets.
let nibName = "AOModalStatusView"
var contentView: UIView!
Now we are ready to add setUpView()
to the file. Add the following code after the last initializer that we wrote.
private func setUpView() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.nibName, bundle: bundle)
self.contentView = nib.instantiate(withOwner: self, options: nil).first as! UIView
addSubview(contentView)
contentView.center = self.center
contentView.autoresizingMask = []
contentView.translatesAutoresizingMaskIntoConstraints = true
headlineLabel.text = ""
subheadLabel.text = ""
}
There’s a good amount going on here, so I’ll go over it real quick! First we need to set the variable contentView
to be the view inside out our XIB file that we created. This is done by accessing the Bundle for this framework, the NIB (which references a compiled XIB), and finally the view inside of the NIB.
Following that, we want to add the contentView
that we just created to this class’s view by using addSubview(contentView)
.
We’ll then set the frame of the contentView
equal to the bounds of the parent view in the class. After that, we want to tell it that we don’t approve of any resizing, since this view has a specific size. That is why we passed the autoresizingMask
an empty array.
Finally, we want to set the labels to have empty text inside, so that our framework is completely customizable.
In AOModalStatusView.swift
add the keyword public
to the beginning of the class so that we can reference it directly later on.
We are almost done with our framework! One last thing before we can build and use our framework. Remember how we marked our outlets as being private? Add these three functions so that the framework’s users can set the image and label outlets from their code.
// Provide functions to update view
public func set(image: UIImage) {
self.statusImage.image = image
}
public func set(headline text: String) {
self.headlineLabel.text = text
}
public func set(subheading text: String) {
self.subheadLabel.text = text
}
Voila! We have a framework, and it didn’t take too long to put it all together! But it doesn’t do much by itself, so let’s test it out!
Using Custom Frameworks
We’re going to create a new project to test out the framework. In my opinion, the Internet has plenty of cats, but it could definitely use some more puppies! I’m going to mock up a quick puppy-photo application. Let’s call it Puppy Paradise!
The starting point for Puppy Paradise can be downloaded from my GitHub .
Go ahead and open the starter project in Xcode.
Getting Started
Go to your Project Inspector’s General tab and scroll down to where it says, “Embedded Binaries.” Click the + button and then Add Other
. A Finder window will drop down, and here you need to select the AOModalStatus.xcodeproj
framework that we created earlier. You’ll notice that our Framework was added to the Project Navigator!
Now go back to Embedded Binaries and click the + button again. This time you will see the framework that we added! Click on it to add it to the project’s Embedded Binaries.
Give the project a quick build (command+B) to make sure that things are going well so far! There shouldn’t be any errors yet.
Now that we have our own framework, we’ll need to make sure that Xcode is actually referencing it before we can start coding towards it. Jump to the ViewController.swift
file and import AOModalStatus
just below import UIKit
.
ViewController.swift
has an action that is performed when the user hits “Save” while viewing a picture. Add presentModalStatusView()
inside of the saveTapped(_:)
function. Now let’s add the presentModalStatusView()
function to the class so that the compiler will leave us alone.
func presentModalStatusView() {
let modalView = AOModalStatusView(frame: self.view.bounds)
let downloadImage = UIImage(named: "download") ?? UIImage()
modalView.set(image: downloadImage)
modalView.set(headline: "Downloading")
view.addSubview(modalView)
}
A few things happened here. First, we initialized an AOModalStatusView
and named it modalView
. Then we created an UIImage
using the “download.png” image stored in the assets. Then we set the image and some text to the view and added it to the ViewController
as a subview.
Build and run the application on the simulator and try to save the photo! And it works! Except, it seems to be lacking in a few areas. For example, we can’t close it. There also aren’t rounded corners. And on top of that, Apple uses a subtle animation to display and remove their modal status view.
Well, it turns out that these aren’t too hard to implement! Since we imported the framework’s project instead of just the compiled framework, we can make changes to it and Xcode will compile it every time we build PuppyParadise.
Timer
Tap the drop down next to the framework’s project and navigate to the AOModalStatusView.swift
file that we created a while ago. The first step is to enable the view to close itself after a given few seconds.
Add var timer: Timer?
after the declaration of the contentView
variable. Then add the following code:
public override func didMoveToSuperview() {
// Add a timer to remove the view
self.timer = Timer.scheduledTimer(
timeInterval: TimeInterval(3.0),
target: self,
selector: #selector(self.removeSelf),
userInfo: nil,
repeats: false)
}
@objc private func removeSelf() {
self.removeFromSuperview()
}
This is some simple code to say, “Get rid of this view after three seconds have passed.”
Rounded Corners
Next is rounded corners, which drastically reduce the sharpness of the AOModalStatusView
. Add the following code to the class as well:
// Allow view to control itself
public override func layoutSubviews() {
// Rounded corners
self.layoutIfNeeded()
self.contentView.layer.masksToBounds = true
self.contentView.clipsToBounds = true
self.contentView.layer.cornerRadius = 10
}
This is the typical code that can be used to create rounded corners. The important parts to mention are that this is occurring after the subviews (AKA contentView
) have been laid out and that we are setting clipsToBounds
to true
, so that the subviews contained within contentView
cannot slide out from behind the rounded corners.
Now give it a run! When we go to save our adorable puppy photo it disappears after a few seconds, and we have rounded corners.
Now there’s one thing left to do to make it really “pop!” Let’s add some animation.
Animation
It’s understandable that many people are afraid of working with animations in Swift, but I’m going to show you how to do something so simple and effective that you’re probably going to look up an animations tutorial right after this!
Replace the didMoveToSuperview
and removeSelf
functions with the following:
public override func didMoveToSuperview() {
// Fade in when added to superview
// Then add a timer to remove the view
self.contentView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.15, animations: {
self.contentView.alpha = 1.0
self.contentView.transform = CGAffineTransform.identity
}) { _ in
self.timer = Timer.scheduledTimer(
timeInterval: TimeInterval(3.0),
target: self,
selector: #selector(self.removeSelf),
userInfo: nil,
repeats: false)
}
}
@objc private func removeSelf() {
// Animate removal of view
UIView.animate(
withDuration: 0.15,
animations: {
self.contentView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.contentView.alpha = 0.0
}) { _ in
self.removeFromSuperview()
}
}
And then add contentView.alpha = 0.0
to the end of setUpView
.
What is happening here isn’t all that complicated. In didMoveToSuperview
we are transforming contentView
to half its width and height. Then we run an animation with UIView.animate(withDuration)
. Inside of this we set the animation to CGAffineTransform.identity
which is a fancy way of saying, “how it should normally look (AKA not half its size).” We also set the alpha value back to 1.0 inside the animation. This tells contentView
to grow from half its size as it fades in during the animation. After the animation finishes, we set the timer that was originally created immediately.
In removeSelf
we are doing the opposite. During the animation, we want the alpha value to drop back to 0.0 (or transparent) and the transform to again make contentView
half its usual size. After the animation is finished we can get rid of the view altogether.
I want to point out that ALL this code is happening inside of the framework, and that when we go to use it in a project we still only need a few generic lines of code.
Now give the project a run and try to “save” that photo. You should now have a working framework and project to test that framework. More than that, you can reuse this framework in another project, or even push the code to GitHub for other people to start using!
You can download the final versions of Puppy Paradise and AOModalStatusView off of Github!
How did it go? It may have been a lot of work to get it going, but every time you write a new framework it’ll only get quicker and easier!
I’d love to read your comments below! There are many ways to go about writing a framework, so let me know if you did something different. You can also reach out to me on Twitter at @_alecoconnor.
Thanks for reading, and happy coding!