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 View public 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 .

Can I get a collective, “Awe!”

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!