RE-USABLE CUSTOM UIVIEW & XIB'S

  • Swift 3
  • Xcode Version 8.0 beta (8S128d)

Click here if you prefer to read this article on Github. I'm not a huge fan of how SquareSpace renders the code snippets in MarkDown, so feel free to read this on Github.

Lets make a re-usable custom view using XIB's.

reusable

Instructions

XIB File

  • First, lets create the XIB file. File --> New will present us with this window:

FirstWindow

  • Select User Interface under the iOS heading on the left and his Next. I named the file WeatherView. After doing so, you should see the WeatherView.xib file in the navigator pane on the left.
  • Select the WeatherView.xib file, you should be presented with the following:

XibView

  • In the Utilities Menu, which you open up by selecting the right-most button in the top right of Xcode, select the Attributes inspector which will bring up various options.
  • Underneath the Simulated Metrics, we want to change the size to freeform:

freeform

  • Now going to the Size Inspector (icon that looks like a ruler next to the Attributes Inspector), I want to set the Height & Width of this View on screen. I'm setting the Width to equal 600 pts, and the Height to equal 300 pts like so:

HeightAndWidth

  • I want to stick to these proportions even though the UIView object that will have its Custom Class set to this particular WeatherView object might not be 600w x 300h. When I later create this UIView object in the Main.storyboard file, I will want to stick to these proportions (or make the constraints equal to 600w x 300h to match this). This might not make too much sense now, come back to it when this is all complete.

  • Next, you design your view. I've already done the work here (I won't step through how I did it as it's not the point of this blog). You can check out the project here.

Layout


View Class

  • Lets create a new Cocoa Touch Class like so:

NewFile

  • Make sure it's a subclass of UIView. I'm naming this view WeatherView.

UIViewSubclass

  • The contents of your WeatherView.swift file should look like this:

WeatherView

  • Next, I added the following methods:
import UIKit

final class WeatherView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        //TODO: Do some stuff
    }

}
  • To take advantage of Static Dispatch (in that this WeatherView class will NOT be sublcalssed, I marked it final).
  • init(frame:) is inherited from UIView, WeatherView is subclassed from UIView. Here, we're overriding its implementation first calling on super's implementation passing in the frame we receive in as an argument.
  • init?(coder:) is inherited from UIView, but it was marked as required in UIView's implemntation, because of that.. we are required to mark it as required as well. Similar to init(frame:) we are first calling on super's implementation passing in the NSCoder object we receive.
  • You'll notice that both methods just described both call on commonInit() which is marked as a private function. Marking it private only allows for people within this particular .swift file (WeatherView.swift) to call on this method. This protects us from having this function get called from any other file.

Hopefully, I have your full attention. If not (don't just keep reading). Go back and re-read any sections you glossed over. In making sure you understand this material, it's important that you understand every step of the way.

TimAllen

Lets go back to the XIB file for a moment.


Back to the XIB file

  • In the WeatherView.xib file, I'm selecting the File's Owner in the left pane like so:

FileOwner

  • When File's Owner is selected, take a look in the top right underneath the selected Identity Inspector. Under Custom Class we can open the drop down menu under the option Class or type something in that box. Lets set it to our WeatherView class file.

SetupView

  • Bring up the assistant editor now (I like mine split where I have the code portion at the bottom and the view elements on top):

AssistantEditor

  • Option drag from the View seen here in the following screenshot into your code as an Outlet and name it contentView.

view

  • After doing that, setup all of the other UILabel's and UIImageView like so:

outlets

  • Now we can access these view elements in code! Lets go back to the WeatherView.swift file now.

Back to the View Class

  • Lets go back to the WeatherView.swift and implement the commonInit() method that is marked with a TODO.
private func commonInit() {
        // 1. Load the nib
        Bundle.main().loadNibNamed("WeatherView", owner: self, options: [:])

        // 2. Add the 'contentView' to self
        addSubview(contentView)

        // 3. Constraints to be done in code                            contentView.translatesAutoresizingMaskIntoConstraints = false

        // 4. Setup constraints
        contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    }
  • I'm going to touch on 2. for a minute. The .xib file itself contains a UIView object (which we've been adding other UIView objects to.. the multiple labels and the imageView). The constraints on those various view elements were made to what is now named contentView which is the UIView object in the .xib file. But.. here we are inside of WeatherView class, which is a sublcass of UIView which means it IS a UIView. Ok.. so we have two separate UIView's here, but we know that the WeatherView acts as the owner of the UIView now named contentView in the WeatherView.xib file.

stickwithme

Stick with me.

  • Whenever an instance of our WeatherView view object is created (whether that be in code or Interface Builder), commonInit() will be called on that instance. When that occurs, we load into memory the WeatherView.xib file which hooks up all our outlets as the WeatherView.swift file is its owner. Then, we add contentView as a subview to self, self being the newly greated instance of WeatherView (again.. that will occur either in code somewhere or in Interface Builder which we will do later). The subview we're adding to self here is the contentView which contains ALL of those view elements we created. But.. how will that contentView constrain itself to the view it's now be placed inside of. It doesn't know how to do that so we set the translatesAutoresizingMaskIntoConstraints property on the contentView to false which allows us to programtically create constraints on it. So we do that. We then set the constraints of the contentView to equal self's top, bottom, left, and right anchors. That constrains the contentView to fit perfectly into self (self again being the instance of WeatherView).

  • Lets put this in action.


Storyboad

  • To demonstrate how this will work, go to the Main.Storyboard file and drag out a View onto the canvas, like so:

storyboard

  • Here, I'm setting up the constraints of the view we just dragged in like so (making sure to adhere to that 2:1 proportion of constraints we created in the .xib file (600w, 300h) which looking back was a little aggressive. I should have kept the ratio but made it slightly smaller.

constraints

  • Now select that View object that we just added constraints to and open the Identity Inspector like so:

identityf

  • Set the Class there underneath the Custom Class heading to WeatherView

setuptheview

  • That's it.

  • Build & Run:

blop

Debugging in Xcode (Objective-C)

BUGS

Your code will break and it will break a lot.  Using Xcode, you can add breakpoints to determine where in your wonderfully written code it's breaking.  

 

 

Simple Breakpoints:

Adding a simple breakpoint (seen from screenshot above).  When you go to run your app, it will hit the breakpoint(s) you placed when it reaches this point in the execution of your app.  To remove the breakpoint, you can simply drag it off the line number you placed it on.  Keep in mind that you can add breakpoints in your code after you've hit run as well as it's sitting idle at a previous breakpoint.  Adding a breakpoint is as simple as clicking the line number next to the code where you would like your code to pause.  As it's paused on your breakpoint, you can print objects in the debugger and even call methods on various objects.  If Jim is an instance of a Student class, I can type "po Jim" in the console to see if Jim actually exists (an instance has been created).  You can even call methods as in "po [myArray objectAtIndex: 0]".  If a variable should be 25 at a certain point, you can add a breakpoint then po that variable and see what the value is in the console.

 

Conditional Breakpoints:

First add a breakpoint to your project.  Then right click the breakpoint, click "Edit Breakpoint....".  Then where it states Condition, you can put in any condition you like so that way the application will stop when it meets the condition you inputted here.  You've just created a conditional breakpoint!  In a quick example, I will put this to use so you can see how it might be useful to you.  If there's a class of hundreds or even thousands of students and there seems to be a problem in your for loop where you don't know why it's not adding something correctly, by simply adding a breakpoint - you will have to step through the method over and over to try to find where the problem is, this is not efficient nor best practice.  By editing the breakpoint to add a condition like I inputted above, it will run through the for loop (where we placed the breakpoint) until the condition is met.  See the example below:

 

The NSLog statement is printing 3,080.  This can't be possible, with 5 students and 20 labs that can be completed by each student, the maximum amount labsCompleted can equal is 100.  Some student either hacked our system or there was input error.  We will add a conditional breakpoint on line 29 adding the condition (as seen from the screenshot above - "student.labsCompleted > 20".)

After adding our conditional breakpoint and re-running our project, we are met with the following in our console:

Our bug has been found!  It seems Jim is trying to inflate the number of labs he completed in his class.  Either way, we didn't have to step through every single student in the for loop to find out what was causing our figure to be so out of whack.  

 

Symbolic Breakpoints: 
Symbolic breakpoints allow you to set breakpoints based on a symbol, like a method or function name regardless of where it might appear in cold or wherever specific selectors are sent messages.  If you would like to know when viewDidLoad is called on (every single time) then you would add a symbolic breakpoint for "viewDidLoad".  It will even show you the line number it's getting called on.

In my quote matching game, I want to see when someone is making their guess as it doesn't seem to be working right.  Here is the method that checks to see if the person guessed correctly:
 

Using a symbolic breakpoint (see screenshot above), we will check to see all the places this is getting called in our project.  We can then properly debug our project if see it getting called at the wrong time or in too many places.  The demo below showcases the symbolic breakpoint.  Running the application, it seems to be getting called at the correct time.  I could have just as easily set a simple breakpoint in this method but for simplicity I'm just highlighting how to use the symbolic breakpoint.

 

Key Frame Animations

Core Animation is a data visualization API on both iOS and OS X that allows you to animate the views and other visual elements of your app.  Most of the work in drawing each and every frame of the animation is done for you, all you have to do is configure a few animation parameters and tell it to start.  At the heart of core animation are layer objects, which we use to manipulate our content.  

How did I make this button spin?  I didn't drop down to core animation itself and draw each frame.  I took advantage of methods that manipulate the underlying core animation layers.  The two methods used were introduced in iOS7 which allowed us to use Key Frames in our animation.   Both these methods used are class methods on UIView -  animateKeyframesWithDuration and addKeyframeWithRelativeStartTime (they go hand in hand).

First, let's establish the key frames.  What do I want to animate?  How do I want to animate it?  Do I want to spin the content, shake it, move it, scale it, transform it?  All of these are possible.  In this example, I'm going to transform (spin) the image.

keyFrame.png

How do I do this in code?  This button has a .transform property which is a structure that holds an affine transformation matrix (google is your friend).  By making our .transform property equal an affine transformation matrix (that differs from the original) then we are transforming our button.  But how do we use this information to make our button spin? We set our .transform property to equal CGAffineTransformMakeRotation (CGFloat angle) .  CGAffineTransformMakeRotation has a parameter in radians, so lets give it some radians.  By passing in Pi ( 3.142 / 180° / π ) we would be transforming our button 180°.  This returns an affine transformation matrix (which is what .transform is looking for) constructed from a rotation value you provide.  M_PI is a constant in Objective-C which is equal to 3.142 (it's Pi).

//1st Key Frame
button.transform = CGAffineTransformMakeRotation(M_PI + (M_PI/4));

//2nd Key Frame
button.transform = CGAffineTransformMakeRotation(M_PI - (M_PI/4));

//3rd Key Frame
button.transform = CGAffineTransformMakeRotation(0);

 


The first method we call to establish some rules to how we run through our key frames:

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration
                               delay:(NSTimeInterval)delay
                             options:(UIViewKeyframeAnimationOptions)options
                          animations:(void (^)(void))animations
                          completion:(void (^)(BOOL finished))completion

 

The method we use (three times in my example) to establish our key frames and when they kick off.  This method is called inside of the animations block in the method above.

+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime
                        relativeDuration:(double)frameDuration
                              animations:(void (^)(void))animations

Running through the slides:
The duration passed in of 0.6 seconds dictates the entire duration of the animation.  It's how long it will take to run through all of the key frames I establish.  If I passed in 10 seconds, it would take 10 seconds - it governs the animation.  Delay is what you would think it is, whatever thing you have kick off this code, whatever you pass into delay - it will wait that many seconds before running through the key frames.  Options allows us control in HOW we run through our key frames.  Linear interpolates between the data points, where it creates new data points within a range of a set of data points (this just means that it looks smooth running from one point to the next).  If we passed in discrete then instead of interpolating between the points we would jump from one frame to the next (this means that it would look very choppy).  

Now we come across our addKeyframeWithRelativeStartTime method.  In our first method call we pass in a relateStartTime which LOOKS up to the 0.6 seconds we passed in.  Like I stated earlier, the 0.6 seconds governs the entire animation.  We can pass in a value from 0-1 here.  This just means that, of the 0.6 seconds have to work with - at what point in that 0.6 seconds do we kick off this particular key frame?  Here I passed in 0.0 which means of the 0.6 seconds we're working with, KICK this off RIGHT AWAY.  The relative duration passed in is 1/3 (which is a value from 0-1).  What does that mean?  It means that it will run for 0.2 seconds (1/3 of the 0.6 seconds which governs the entire duration).  So our first frame kicks off at 0.0 seconds of the 0.6 seconds duration and runs for 0.2 seconds (from it's original state to the 225° rotation).  The 2nd frame kicks off at 0.2 seconds (1/3 of 0.6 seconds) and also runs for 0.2 seconds (1/3 of 0.6).  The 3rd frame starts at 0.4 seconds (2/3 of 0.6) and also runs for 0.2 seconds (1/3 of 0.2 seconds).

Then we hit our completion block which will run AFTER our animation completes.  We can perform additional animations here or do nothing.

 

Protocols and Delegates

In the real world, there are times where you are required to follow strict procedures when dealing with certain situations.  Law enforcement officials are required to "follow protocol" when making enquiries or collecting evidence.  A child is required to "follow protocol" when knowing if it's OK to eat another cookie.  My roommate is required to "follow protocol" when making a decision as to whether or not he should be running through the apartment at 3 AM.

In the world of object-oriented programming, it's important to be able to define a set of behavior that is expected of an object in a given situation.  As an example, a table view is expecting to be able to communicate with a date source object in order to find out what it is required to display.  The data source object must respond to a specific set of messages that the table view might send.  The data source could be an instance of any class (for example, NSObject).  In order for the table view to know whether an object is suitable as a data source, it's important to be able to declare that the object implements the necessary methods.   Objective-c allows you to define protocols, which declare the methods expected to be used for a particular situation.

Protocol: An interface.  It's a promise that in anything that conforms to the promise will implement a set of methods.
Delegate/Data source: An object that implements the interface.  The data source supplies the data, the delegate supplies the behavior (which is what I discuss below).  This helps us decouple our objects.

Jim in this example is a child who is looking to either go in the pool, eat some cookies or play some video games.  He needs to first ask someone for permission.  This example shows how various classes are conforming to a protocol which in this case is conforming to "JimDelegate" as you will see in the code.  By doing this, Jim can ask anyones permission who conforms to the JimDelegate to do the things he wants to do. 

Below is the Jim.h file and Jim.m file (you will see in the .m file that Jim is calling on methods implemented by the delegate, we will get to that later).  Jim can ask permission to jump in the pool, eat a cookie or play video games.  Jim has a property named delegate of type id (meaning this could be of any class) which conforms to the JimDelegate.  But how do we create the JimDelegate?  See Below.

 

This is the JimDelegate.h file.  "@protocol ProtocolName <ProtocolToExtend>" is the line of code required in order to create a protocol.  Any objects that adopt this protocol are guaranteed to implement all of the below methods.  The <NSObject> after the protocol name incorporates the NSObject protocol, not to be confused with the NSObject class.

 

Below is an object (of type NSObject) called Maryann.  You will see in the Maryann.h file that she conforms to the protocol.  That is done with the following: "@interface Maryann : NSObject <JimDelegate>"  We will then bring in that delegates methods and implement them in the Maryann.m file as you will see below.

 

The below code is written in the AppDelegate.m main file in the didFinishLaunchingWithOptions method to show what is going on here.  We are creating a Jim Object named jim and a Maryann object named maryann.  For now, Aunt Pat has been commented out (her .m & .h files are identical to Maryanns).  The following methods being called on by jim are producing the three NSLog statements below.  Easy enough, but I'm showcasing this code to show how easy it is to have Aunt Pat become the delegate instead of Maryann (my mom) when my Mom has to go away somewhere (like go shopping and telling Aunt Pat to not let me use the computer).  If we didn't set up the delegate this way, then we would have to manually go throughout Jim's .h & .m file and change all of the code to directly speak with Aunt Pat.  Instead, with the below code we are able to easily setup our delegate which would be someone who conforms to the JimDelegate protocol. 

RECURSION, RABBITS and FIBONACCI

 

Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem (as opposed to iteration).  Most computer programming languages support recursion by allowing a function to call on itself.

A common computer programming tactic is to divide a problem into sub-problems of the same type as the original, solve those sub-problems, and combine the results.  This is known as a divide-and-conquer method.  The job of the recursive cases can be seen as breaking down complex inputs into simpler ones.  In a recursive function, with each recursive call, the input problem must be simplified in such a way that eventually the base case mush be reached.  A base case would be like reaching the bottom of the stairs (if you started from the top and and the stairs weren't endless).  After reaching the bottom of the stairs, we will make our way back up to the top, one step at a time.  The following is a brief story about Fibonacci, rabbits and an example of a recursive method done in objective-c. 

Fibonacci, also known as Leonardo of Pisa, invented the Fibonacci sequence while studying rabbit populations.  Assuming a newly born pair of rabbits, one male, one female, are put in a field; rabbits are able to mate at the age of one month so that at the end of its second month a female can produce another pair of rabbits; rabbits never die and a mating pair always produces one new pair (one male, one female) every month from the second month on.  How many pairs will there be in one year?  How many pairs will there be in 10 years?  The answer to those questions are now referred to as the Fibonacci numbers or Fibonacci sequence.  At the end of the nth month, the number of pairs of rabbits is equal to the number of new pairs (which is the number of pairs in month n - 2) plus the number of pairs alive last month (n - 1).  This is the nth Fibonacci number.  This is the Fibonacci sequence in mathematical terms:

Fn = Fn-1 + Fn-2


What if we wanted the 6th Fibonacci in this sequence, how would we get it?  With computers, we no longer need to reference rabbit love making to come up with that number.  One way is using the recursive function where we will write a method (in this example, using Objective-C) where it will call on itself multiple times in producing whatever number we pass into it.  If we want the 6th Fibonacci number, we will pass 6 in as number in this method.  But what if we wanted the 2,000th Fibonacci number?  Using a recursive method like this would be highly inefficient.  As there are optimal ways of writing a method to produce the nth Fibonacci number (which are clearly more efficient than this recursive function), I am showcasing this particular method to give an example of what a recursive function looks like and how it behaves.  One better way to solve this problem would be to store the Fibonacci numbers in an array where the next item in the array will always equal the sum of the previous two items.

- (NSInteger)findFiboNumber:(NSInteger)number {
  if (number <= 1) {
    return number;
    }
  else {
    return ([self findFiboNumber:number - 1 ] 
           + [self findFiboNumber:number - 2]);
    }
}

This method will return a NSInteger number.  The argument to this method (which is of type NSInteger) will be where we pass in the number 6.  What will be returned after all is said in done, will be the number 8.  Below is a visualization of how we get to 8 after passing in 6.

RECURSION TREE
a visualization of the recursion method written above

  • The circled numbers represent what figures are being passed through the method (at various stages).
  • The red numbers below the circled numbers represent what figures are being returned.
  • The final returned number is 8 (the red figure below the circled 6 which began the method).