Use Test Flight

One of the really cool features of the App development process is getting real users to beta test your app. iOS  has a great way to do this with the test flight app. This week, I’ll summarize the steps to set up Test Flight for your app to beta test. I’ll be using my own app MiFitnessTrails in Beta, so there’s no exercise files this time. 

Your first step is to make an App ID. You’ll need to have a developer license for this. Go to your accounts at developer.apple.com/account and select the Certificates, ID’s and profiles button,

Then click App ID’s You see your current App ID’s.

I tend to use a specific App ID for projects I’m producing, and you can make one by pressing the Plus at top.

Register the App Fill in the information requested, using the bundle identifier for your app, your Developer ID, and the services you need. I’m keeping to the default services for now.  That will set up a record like this:

Your second step is to set the app up in App Store Connect as you would an app you are publishing.

Go to My Apps and hit the plus button.

Select New App in the drop down that appears

You get the App registration screen.

Once completed and you press Create, Apple will process your request and set up an entry in App Store Connect. You’ll go into that entry. The first screen is for preparing for App Store permissions, and Its a good idea to fill out as much as you can here for final publishing.


You’ll see a tab for TestFlight. Click that.  On the sidebar, click Test information. Fill out a description, an email for responses from your testers and a Marketing URL. While marketing documents can be here, if you are still working on user documentation, this is the URL your testers can get them from. Also include a privacy policy URL if you are collecting or sharing information. 

App review works differently for Test Flight than for the App store. You are only reviewed once for each test group or version, with your initial build submission. Place your review information here. After your first build is reviewed , you can deploy new builds directly, without approval. 

Your next steps are in Xcode.  Under Targets, make sure you have all your versioning and build info correct for submission. Builds must be unique each time.  I need to change my build number to 14.

Set your simulator to Generic iOS Device. You need to archive the project and you can only do that wiht this setting or connected to a live device. Select Product>Archive and archive the app.  Go to the Archives, which might show up for you after archives or Select Window>Organzer(Option-Shift-Command-O). Hit the Archives tab at top.

Select the top archive for build 14. You can validate or distribute the app. I tend to use Validate first, so I can catch any errors I made before submitting. In actual workflows, it saves a lot of time, doing all the steps of a Distribute locally so you can correct your mistakes a lot faster. I know this one works, so I’ll click Distribute

Leave all the defaults of iOS AppStore and Upload, then I leave all these defaults checked

In the next question. I automatically manage signing. Hit next one last time. Wait as the archinve is processed. I’ll get a summary screen when done. When Processed, I’ll upload it.

This will take some time, as much as ten minutes to an hour depending on the size of your files, so again do some waiting.  

While we are waiting, head back to App Store Connect. I’ll set up testers. You’ll see on the side a button New Group

If you Click that. It show another form for creating the group. I already set up a MakeAppPie list.   Since creating a new group would mean a two-day review, I’ll skip creating a new one and show you the created one. There are two tabs, a Testers and a Build tab.

In the Testers tab, you list your testers. All testers need a first name, last name and e-mail address. You can manually add testers, import a file or use the Public Link to get users to join. For example, If you wanted to join this beta test, you can go tohttps://testflight.apple.com/join/99XfjB8r . It would add you to this test group.

You’ll need a build for this, and you can and that in the next tab.  Before we do, Let’s check on the build.  go back to Xcode and You’ll find the upload was sucessful.

Press Done to dismiss the window. Head back to iTunes connect and in the sidebar select iOS Builds

You’ll see the new build is there. If not, refresh your browser.

In the MiFitness Trails App, you’ll see the build 14 on top of several other builds. Builds have expiration dates which are 90 days from the submission date.

On build 14, there are more questions about encoding. Click the trangle and you’ll get a link.  

Then You’ll get a question about changes to encoding. Answer the yes/no question and hit Start Internal Testing. Internal testers are people who have priviledges on your Apple developer account. If you have notifications on for Test flight on your phone, You’ll probably get a notification like I did on my watch:

Back at the builds tab in your list, click the add button

You’ll get a list of the available builds.

Select the build you want. Hit Next. You’ll get another question about testing information. If your app goes through App review before test flight distribution, and there are user sign-ons and passwords in the app, Apple would like access to a test account to make sure everything works correctly.

I don’t have sign-on information so I’ll leave that unchecked. At the bottom, I’ll make sure to inform my users by checking the Automatically notify testers button. Hit next. You’ll get the testing information form.

Add the improvements you’ve made to the app and more instructions to the beta testers for what you are looking for. The hit the Submit for review button.

If this is your first upload, you added a group,  or you changed versions, you’ll go through App review and the status of the build will change to Submitted for review. This has taken about one or two days in my experience. Once reviewed and accepted, or if you only changed the build number, this will be ready The build’s status changes to to Testing.

Your beta testers will get a notification of the new build:

When the beta testers go to the test flight App on their device, they can download and install the new version:

That’s the basics of Test Flight. This, of course, can change as Apple changes processes. Without long wait times for approval, with Test Flight you can get an app into a limited number of devices for real-life testing and gain a lot more insight into the effectiveness and improvements needed in your app. Having a large set of beta testers is great for marketing as well. You can start to find core customers for when the app goes live in the App Store.

Using Haptics

If you have an iPhone 7 or 8, you might think the home button is a physical button. If you completely shut off the phone you find it is a solid circle. What makes that click is a haptic. You can easily use haptics in your application to make buttons click or warn the user.  

I’d suggest downloading the exercise file. Haptics only work on a live phone. Take a look at the starter file. I have a simple button.

Right-click the document outline and you’ll see I made a Touch Up Inside and touch down event action for the button. 

Go to the ViewController.swift code. 

There are three types of haptics. The impact generator can be used for a collision of objects on the screen or as I use it a touch of a finger to a button for a click.  At the top of the code add this: 

let impactGenerator = UIImpactFeedbackGenerator(style: .medium)

The style gives one of three options of .light, .medium, or .heavy impact. I’ll use .medium. Impact generators need a small bit of time to get ready, so when I push down on the button, I’ll add the prepare method.

impactGenerator.prepare() //need enough time for this to get ready

Then I can use the impact occurred method to give the illusion I’m clicking down on something. 

impactGenerator.impactOccurred()
impactGenerator.impactOccurred()

When I lift up my finger I don’t feel anything, but to get the click that I’m releasing the button, I can add the impact occurred again. 

impactGenerator.impactOccurred()

To run and experience this code you’ll need a real phone. Simulators don’t do haptics, and neither do websites.  I suggest trying this yourself since this as a physical sensation or check out the video of this tip Run this and tap the switch. You get a feel like you are pushing down a button and then releasing it. 

Stop the app. At the top of the code, add a notification feedback generator. These give more complicated haptics to notify a user of an event. 

let notificationGenerator = UINotificationFeedbackGenerator()

Comment out the impactOccured in clickMeUp .

Add in clickMeUp 

notificationFeedbackGenerator.notificationOccurred(.success)

You have a few options for the type of notification:error success and warning. I’ll use a success. 

 Then run the app again. Press down and you’ll feel the impact click. Release and you’ll get a double tap for success. 

Try .error, run again.  You get a different pattern.

The third type of haptic is a selection feedback generator. It is so subtle it’s hard to demonstrate. It’s the haptic you get when spinning a picker view. 

Haptic can be used for gaming, and for physical feedback, such as alerting users not looking at their phone. Try to use them only when they are necessary, overuse confuses the user. Also, tie them to visual feedback whenever possible. With the proper uses, haptics can add to your applications a true feel. 

The Whole Code

Here’s the code for this project. You can find a download of this at GitHub here. You can also watch this lesson at LinkedIn Learning

//
//  ViewController.swift
//  Haptics demo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {
    let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
    let notificationGenerator = UINotificationFeedbackGenerator()
    @IBAction func clickMeDown(_ sender: UIButton) {
        impactGenerator.prepare()
        impactGenerator.impactOccurred()
    }
    
   
    @IBAction func clickMeUp(_ sender: UIButton) {
        //impactGenerator.impactOccurred()
        notificationGenerator.notificationOccurred(.error)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }


}

Fix Stack View Disasters

Stack views are supposed to help make layout and auto layout easier, but often they will leave you very frustrated. Take a look at this storyboard, which you can download from the exercise files. It’s a mess.


Let’s discuss some solutions for a mess a like this. You’ll see on the error panel I have a lot of errors, much of this is from stackviews being a set of constraints.

But let’s add one more stack view first. Select the Chicken, fish vegetable and beef buttons. Embed them in a stack view. In the attributes, You’ll see the Axis is vertical.

The stack view guesses at attributes and is often wrong. Change to horizontal. Change the Alignment to Fill and Distribution to Fill Equally.

That gives us a different looking stack view.

Now we can get to one of the first important places to check: the spacing. Xcode uses the inherent spacing of the buttons. A large chunk of this mess is this spacing issues, which is causing some conflicting constraints. Open the document outline and select all the stack views.

I’ll change all the stack views Spacing to 8.

That changes a lot:

Select the Stack view details. The alignment here is trailing. I’ll make that fill.

That already looks better, We’ve got some errors still, one set relating to hot and cold. The Temperature stack view Distribution is set to Fill. This leaves some ambiguity among the views, which you can see in teh error panel.

You can do a couple of things here. The first is use Fill Proportionally, and the size of the text dictates the size of the button.

I like using Fill, and use size constraints on the subviews. I’ll make the Hot and Cold buttons equal widths by control – dragging between them on the outline and select Equal Widths.

That give me equal sized buttons

I’d Like more space for my image. I’ll drag up from the soup image in the document outline to the Stack View Image, select Equal Heights

In the size attributes, Change the Multiplier for the constraint to 2/3 so it is 2/3 of the upper stack view

Nothing changes and there are errors here because I’m on Fill Equally distribution for Stack View Image, and need to be on Fill to use sizing. Change that setting and things look better still

We still have errors. Sometimes they come from outside the stack view. Take a look at the Search for Soup button in the size inspector.

It has a proportional height which conflicts with the vertical sizing of the stack view. Delete the size and more errors disappear.

The Add Crunchies!! button is huge. I can change the size of the Add Crunchies!! button and the soup base buttons to be their intrinsic content size, which is the height of the font with some default padding. An easy way to solve this is to grab a empty view, and place it at the bottom of the stack view.

Set its Background color to Clear. The view had no intrinsic size and can stretch and compress much easier the buttons. I still have errors all over the place. This is another distribution error on the root. Change to Fill equally, and it disappears.

This all looks good until you rotate the phone.

That’s why I made two halves in stack views embedded in a stack view. Click the Stack View Root. We can vary the Axis when in landscape on an iPhone. Hit the + next to the Axis. Change this to Any for Width, so we include plus size phones in landscape orientation. Add the variaton

You’ll get an axis variation. Change it to Horizontal

That looks a lot better.

You can keep going like this. The key is to check yourself on other devices and orientations to make sure you don’t get conflicts.

Sliders as Knobs

You’ve seen other apps rotate views, but you may have no idea how to do it yourself. Let me show you one way to rotate views. We’ll make a simple knob control you might be able to use in your apps. Download the example file. On the storyboard, I added an UIImageView of a knob and a label.

In ViewController.swift, I set this up to use touch methods for user interaction. Check my earlier tip on touch and the apple pencil on how to use these methods.

You do translations like rotations using the layer property of UIView. layer is a CALayer, and that has a property transform. In the touchesMoved method inside the braces, assign the transform property of the layer:

knob.layer.transform = 

There’s a couple of ways to do a transform, but I prefer using the 3d ones over others. Add the CATransform3dMakeRotation method CATransform3DMakeRotation(angle: CGFloat, x: CGFloat, y: , z: CGFloat)

knob.layer.transform = CATransform3DMakeRotation(angle: CGFloat, x: CGFloat, y: , z: CGFloat)

It has four parameters. The first is the,angle and I’ll put a variable named angle there for the moment, and come back to it. Next, I have x, y, and z, which is the axes I’ll rotate through. To avoid a semester of Linear Algebra, I’ll keep this simple. If these values are zero you don’t rotate on that axis. If one, you rotate.

If you use the x-axis, which is a horizontal line on the device you’ll flip the knob. The vertical line is the y-axis, which also flips the knob. To rotate in the same plane you want the line extending toward the user, the z-Axis.

I’ll set the axis to z rotation on and keep x and y off.

CATransform3DMakeRotation(angle, 0, 0, 1)

Now back to angle. Like most places in iOS, it is in radians. While most like to think in degrees, there’s a good reason for it to be radians: we can describe all points in the rotation as a number between zero and 2 times pi. Since I can multiply the pi in at the last minute, I can think of numbers only in a range of zero to two, which actually makes using the math a lot easier than 360.

Add a line above to set the angle.

let angle = theta * CGFloat.pi

I’ll use theta as the constant I’ll multiply to pi.

To get a number to use for theta, I’ll use the x position of a touch on the device.

let currentPoint = touch.location(in: view)

To get this to a value between 2 and zero, I’ll divide this by the width of the view, then multiply that by two

let theta = 2.0 * currentPoint.x / view.frame.width

I’ll also print it to the label

 label.text = String(format:"%02.3f pi",theta)

I’ll run this on an iPhone 8 simulator, which seems to handle animation better than an iPhone X. Dragging my mouse over the simulator which simulates touch makes the knob move. You’ll see that higher values rotate clockwise, lower ones counterclockwise.

I can also limit the angle by reducing the multiplier from 2 to some smaller number, say 1.5 for a three-quarter turn.

let theta = 1.5 * currentPoint.x / view.frame.width

I can also add a rotation offset. I’ll limit the knob counterclockwise 90 degrees which is subtracting 0.5

let angle = (theta - 0.5) * CGFloat.pi

Build and run. You get a 3/4 turn of the knob limited in different places. Since touch is not completely accurate even on the sides, you might not get a maximum value.

There’s more you can do with this, but this gives you a few ways to explore with rotating views.

Time Picker Part 2: Strings to Time Intervals

In the last tip,  I showed you how to use UIPickerControllers to make a time interval input. What I didn’t show you is how to output that. In this tip I’ll show you how to read and convert data from a picker that has a separate delegate. 

If you download the exercise file, you’ll have a copy of the project as I left it last week. If you go to TimeComponents, you can see the first problem: this is a delegate of a picker. It does not communicate directly with a view controller. I’m going to set a delegate for this that has two parameters: the time in a string and a time interval. 

protocol TimeComponentsDelegate{
    func didSelect(timeString:String,seconds:TimeInterval)
}

Then assign the delegate to TimeComponents

var delegate:TimeComponentsDelegate! = nil

In pickerView( didSelectRow:), I’ll add this to update the values whenever we change a component.

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
       delegate.didSelect(timeString: resultString,seconds: timeInterval(resultString))

    }

How do I get the String and the TimeInterval? It is easier to get the string first and convert it to seconds. I’m going to make a property resultString

Every selection,  I’ll clear it. 

resultString = ""

Then loop through the components

for index in 0..<componentMax.count{
}

Picker views have a method selectedRow(inComponent) I’ll grab the selected digit in each component as I loop through 

let digit = pickerView.selectedRow(inComponent: index)

If the component is a separator, I’ll add the separator to the string. Otherwise, I’ll add the digit.

if componentMax[index] == 0 {
  resultString += separator
} else {
  resultString += String(format:"%i",digit)
}

That gets me a string.  I’ll use another method for converting the string to a timeInterval. I will separate out the components by the separator, then add the value of the hours minutes and seconds together. 

Under the didSelectRow, add this 

func timeInterval(_ timeString:String)-> TimeInterval!{
        
    }

I’ll add an array of multipliers in seconds for hours (3600) minutes(60) and seconds.  I’ll also initialize time to 0.0

let multipliers = [3600.0,60.0,1.0]
var time = 0.0

I’ll split the string into an array using the separator to split it up. 

var timeComponents = timeString.components(separatedBy: separator)

I’ll check to make sure the components array is the same length as the multipliers.  

if timeComponents.count > multipliers.count {return nil}

.If so, I’ll loop through the components. I’ll use a guard while casting them to TimeInterval That will also check for a valid value before multiplying by the multiplier.  If invalid I’ll return nil.

for index in 0..<timeComponents.count{
            guard let timeComponent = TimeInterval(timeComponents[index]) else {return nil}
            time += timeComponent * multipliers[index]
        }

I’ll return the TimeInterval after the loop. 

return time

With all that in place. I’ll set up the delegates in the view controllers.  I’ll get a message about the required method. Use the Fix there to load the required method. I’ll set it up to display timeString

class ViewController: UIViewController,TimeComponentsDelegate {
    func didSelect(timeString: String,seconds:TimeInterval ) {
        self.title = timeString
    }

Don’t forget to point the delegate correctly. 

timeComponents.delegate = self

Then I’ll do the same to the SecondViewController, this time displaying the time interval:

class SecondViewController: UIViewController,TimeComponentsDelegate {

func didSelect(timeString: String, seconds: TimeInterval) {
title = String(format: "%6.0f seconds", seconds)
}

Note I’m living dangerously here, and not worrying about having nil values. Don’t forget to point the delegate correctly. 

timeComponents.delegate = self

Build and run. On the first view controller your time shows up on top. 

Click the button, and try 1:01:01 on the second, and you get  3661 seconds. 

Using the techniques from this and the last tip, you can make a powerful picker for selecting times. 

The Whole Code

Here you’ll find the code for this project. You can also download it from GitHub here:

TimeComponents.swift

//
//  TimeComponenets.swift
//  ProtocolPickerDemo
//
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit
protocol TimeComponentsDelegate{
    func didSelect(timeString:String,seconds:TimeInterval)
}
class TimeComponents: NSObject,UIPickerViewDataSource, UIPickerViewDelegate {
    let componentMax = [9,9,0,5,9,0,5,9] // 0 = separator
    var separator = ":"
    var delegate:TimeComponentsDelegate! = nil
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return componentMax.count
    }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return componentMax[component] + 1
    }
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if componentMax[component] == 0{
            return separator
        }
        return String(format:"%i",row)
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        var resultsString = ""
        for index in 0..<componentMax.count{
            let digit = pickerView.selectedRow(inComponent: index)
            if componentMax[index] == 0 {
                resultsString += separator
            } else {
                resultsString += String(format:"%i",digit)
            }
        }
        
        
        delegate.didSelect(timeString: resultsString, seconds: timeInterval(resultsString))
        }
    func timeInterval(_ timeString:String)-> TimeInterval!{
        let multipliers = [3600.0,60.0,1.0]
        var time = 0.0
        var timeComponents = timeString.components(separatedBy: separator)
        if timeComponents.count > multipliers.count {return nil}
        for index in 0..<timeComponents.count{
            guard let timeComponent = TimeInterval(timeComponents[index]) else {return nil}
            time += timeComponent * multipliers[index]
        }
        return time
    }


}

ViewController.swift

//
//  ViewController.swift
//  ProtocolPickerDemo
//
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit


class ViewController: UIViewController, TimeComponentsDelegate {
    func didSelect(timeString: String, seconds: TimeInterval) {
        self.title = timeString
    }
    
    
    
    @IBOutlet weak var picker: UIPickerView!
   let timeComponents = TimeComponents()
    override func viewDidLoad() {
        super.viewDidLoad()
       picker.delegate = timeComponents
        picker.dataSource = timeComponents
        timeComponents.delegate = self
    }




}



SecondViewController.swift

//
//  SecondViewController.swift
//  ProtocolPickerDemo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit


class SecondViewController: UIViewController, TimeComponentsDelegate{
    func didSelect(timeString: String, seconds: TimeInterval) {
        self.title = String(format:"%6.0f seconds",seconds)
    }
    


    @IBOutlet weak var picker: UIPickerView!
    
    let timeComponents = TimeComponents()
    override func viewDidLoad() {
        super.viewDidLoad()
        picker.delegate = timeComponents
        picker.dataSource = timeComponents
        timeComponents.delegate = self
    }
    


}

Protocols and Time Pickers

While there is the date components picker, sometimes you want a picker the gives a time interval in seconds. In this two-part tutorial, Let me show you how to set up one, and discuss some good uses of protocols beyondself

If you download the exercise files, you’ll see I set up a project with two pickers and set outlets for them. I also set up two view controllers and another Swift file I named Time components.

If you are familiar withUIPickerViews, you know that they use a delegate and data source to set the components and return values. Usually, you set the delegate and data source toself. However, that means you set the component values in each view controller. I want for my project to have hours, minutes and seconds on every picker. Instead of writing the code for that several times, I’ll make a separate object that has the delegates and data sources, then use it in both view controllers.
Go to time components. You’ll see I already added the delegate and data source methods for you to add the code. There are several ways I usually do this depending on the flexibility I need from the picker, but the easiest starts with an array of the maximum value for each picker component.

let componentMax = [5,9,0,5,9,0,5,9]// 0 = separator


I’ll also set a string to be my separator. I’ll make this avar so I can change it later.

var separator = ":"


I’ll put a zero for anywhere I want a separator in the components. For the numberOfComponents, I’ll return the count for that array

func numberOfComponents(in pickerView: UIPickerView) -&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; Int {
return componentMax.count
}


Next, I’ll define the number of rows incomponent To account for 0, that’s one more than the componentMax element. I’ll return

return componentMax[component] + 1


I’ll use a simpletitleForRow method to display this. IfcomponentMax was 0, return the separator string.

if componentMax[component] == 0 {
return seperator
}


Otherwise, return a string of that row value.

return String(format: "%i", row)

That sets up the display part. Head to theViewController.
Add a constant timeComponents

let timeComponents = TimeComponents()


InviewDidLoad, add the delegate and data source to be TimeComponents. When the picker needs them, It will use this object.
Do the same for theSecondViewController. Add the object

let timeComponents = TimeComponents()


Then set the delegates and data sources.

picker.dataSource = timeComponents
picker.delegate = timeComponents


And do one more thing. Set the separator to a period.

timeComponents.separator = "."

Run this on the simulator. Move the wheels around and you’ll find you can set a time in the range we requested.

Click the button on top and you’ll get the second view controller and can do the same.

You can see the power of using a protocol for more than one view controller. You don’t always wantself when you have a standard case for your delegates and data sources. Of course, you will want the value out of this example. In next week’s tip, we’ll look at that. You’ll convert Strings to TimeIntervals, then send back the data to the view controller.

CALayer Masks

UI doesn’t have to look like a rectangle. You might want a different shape for your icon. You can do that with layer masks. Let’s learn how to set them up.
Download the example files. You’ll find an app with two square buttons.

If you head over to the assets, you see I have a thumbs-up and a thumbs-down image and its inverse image.

I’ll take those images and make them mask images cutting the solid buttons.
Head to the ViewController.swift code. I’ve already added images in the assets folder and assigned them to constants in the view controller.
I also created a function for you configureMask, for adding masks to the buttons.

 func configureMask(button:UIButton, with image:UIImage!){ 

A mask is an area, in our case a CALayer, where its opacity determines visibility to an underlying layer. Solid areas appear, areas with an alpha of zero disappear.
Mask layers are CALayers embedded in another CALayers. Instantiate a new CALayer

let maskLayer = CALayer()

Next, set its frame. This frame will be relative to its view, which will be the button. I’ll start with the origin at (0,0) to start in the upper left corner of the button.

maskLayer.frame.origin = CGPoint(x: 0, y: 0)  

I’ll make the frame the same size as the button. I’ll come back to this in a minute.

maskLayer.frame.size = button.frame.size

Once you have a frame, set the contents of the maskLayer to an image. This is a CGImage so I’ll use the image’s cgImage property to get one.

  maskLayer.contents = image.cgImage

Finally, in the button, add  to the layer’s mask property maskLayer.

   button.layer.mask = maskLayer

I’ll use this method to configure both buttons. For the auto layout I defined, the size of the button is not known until the subviews are laid out. I need that for the mask to be correct. So I’ll add it in the method viewDidLayoutSubviews.

override func viewDidLayoutSubviews() {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: downImage)
    }

I’ll change the mask when we tap a button by changing the button’s mask contents. I’ll do that in the upButton IBAction.

@IBAction func upButton(_ sender: UIButton) {
    upButton.layer.mask?.contents = inverseUpImage?.cgImage
    downButton.layer.mask?.contents = downImage?.cgImage
}

The configureMask method replaces the maskLayer. I could use it to change the button’s mask as well.

@IBAction func downButton(_ sender: UIButton) {
    configureMask(button: upButton, with: upImage)
    configureMask(button: downButton, with: inverseDownImage)
}

We are ready to run. You can use any simulator, but I’ll use an iPad 11 inch for this one. Run, and you get two buttons with the first mask.


Click a button. The mask changes.

Click the other button. The mask changes again.

Stop the app. I added two more backgrounds for you. Go to the storyboard and change the button backgrounds to Tile Floor and Wood table. Run again, tap a button,  and you get those photos masked.

That’s the very basics of CALayer masks. With a few lines of code, you can make masks to make any shape, from icons like these to circles and squares. Since masks are a CALayer, You can even make more complex masks by masking the mask’s layer. Play around with masks a bit, You’ll find them a very useful tool to make any view something other than a rectangle.

The Whole Code

Here’s the code for this lesson. If you want to download the full prjoect you can do so on GitHub.

//
//  ViewController.swift
//  CALayerMask Demo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {
    let upImage = UIImage(named:"thumb_up_48pt")
    let downImage = UIImage(named: "thumb_down_48pt")
    let inverseUpImage = UIImage(named: "invert_thumb_up_48pt")
    let inverseDownImage = UIImage(named: "invert_thumb_down_48pt")

   
    
    @IBOutlet weak var upButton: UIButton!
    @IBOutlet weak var downButton: UIButton!
    
    @IBAction func upButton(_ sender: UIButton) {
       sender.layer.mask?.contents = inverseUpImage?.cgImage
        downButton.layer.mask?.contents = downImage?.cgImage
    }
    
    @IBAction func downButton(_ sender: UIButton) {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: inverseDownImage)
    }
    
    func configureMask(button:UIButton, with image:UIImage!){
        let masklayer = CALayer()
        masklayer.frame.origin = CGPoint(x: 0, y: 0 )
        masklayer.frame.size = button.frame.size
        masklayer.contents = image.cgImage
        button.layer.mask = masklayer
    }
    
    override func viewDidLayoutSubviews() {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: downImage)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
    }

}

Draw Paths in UIViews

In a previous tip, I drew rectangles and circles in a UIView. This time, let’s add lines and curves using paths to draw some toast.

Download the playground I set up for you.  I set up my code to do all the drawing in the drawLines method. Go to the closure.

I set up a drawing frame size to be the entire frame size of the view. I also added a few points in the array to draw the toast.

  let drawPoints:[CGPoint] = [CGPoint(x: 300, y:300),CGPoint(x: 300, y: 500),CGPoint(x: 500, y: 500),CGPoint(x: 500, y: 300)]
        let imagerenderer = UIGraphicsImageRenderer(size: frame.size)

Before you draw, you should set a few properties. Start with color. For the fill color of our toast, I’ll add brown using the setFill method of UIColor.

UIColor.brown.setFill()

Then I’ll add purple for a contrasting stroke color using the setStroke method of UIColor.

UIColor.purple.setStroke()

You can also set the shape of the line cap. You do that in the cgContext of the closure’s context.

context.cgContext.setLineCap(.round)

I’ll do the same with the line join giving a slightly curved look to the joints at lines.

context.cgContext.setLineJoin(.round)

The foundation of the drawing is a path. You make a path and then draw that path. You start a new path with a beginPath

context.cgContext.beginPath()

For the first point in our path, you move to a point. I’ll move to the first point in the array

context.cgContext.move(to: drawPoints[0])

To add a line you use the addLine method. This method specifies the point you are going to, using the current point as a start.

context.cgContext.addLine(to: drawPoints[1])

With one line, we can try out our line. Before we do, draw it first using the drawPath method. drawPath has a paramater using, which has several options for how to draw the path. I can fill the path, leave a stroke or both. I’ll start with the stroke.

context.cgContext.drawPath(using: .stroke)

Run this, and You’ll see a line in the live view.

 

I’ll add another line.

context.cgContext.addLine(to: drawPoints[2])

Once I have two lines I can startusing fills in drawpath. I’ll change to .fill

 context.cgContext.drawPath(using: .fill)

I’ll run again and get a triangle.

The path assumes the endpoint of the path is the beginning point for the fill. This triangle is still open though. To see the stroke better, I’ll make the line a bigger width. You use the setLineWidth method on cgContext, which I’ll get from the context in the closure. So add this, just above the beginPath line.

context.cgContext.setLineWidth(10)

Change the drawPath to .fillStroke to draw a line and a stroke both.

 context.cgContext.drawPath(using: .fill)

Run again, and you’ll see the fill connects even if the shape is not closed.

 

I’ll add one more line:

context.cgContext.addLine(to: drawPoints[3])

Run this. We get a three lines around a filled square.

Next, I’ll add a quad curve, one of several curves. This one has a single control point found in the center of the point. I’m going to at first set it to the midpoint of the curve to make this a line.

context.cgContext.addQuadCurve(to: drawPoints[3], control: CGPoint(x: 400, y: 300))

Run this and you get a square.

 

The control point on a quad curve curves according to the position of the control point.  I’ll move the control point up 100 points.

context.cgContext.addQuadCurve(to: drawPoints[0], control: CGPoint(x: 400, y: 200))

Run again.

And you have a piece of toast. I’ve used Playgrounds here to allow for interactive design, but any UIView can use this technique. Explore lines and quad curves for a variety of applications.

Add an Animated Drawer

You’ve probably seen menus that pop out of the side of an app, but do you know how to make one? With the knowledge of some simple code you too can add these to your application using auto layout.
If you take a look at the example file, I’ve set up a storyboard for you.

 

There’s a few buttons and a subview with more buttons. Open up the constraints.

 

You’ll see I’ve set the blue subview Drawer up with a bottom and a trailing constraint, the width and the height. The trailing constraint is flush with the superview, so when closed, it is closed completely. If you click on the constraint, you’ll also see I set up an outlet for the constraint, I’ll connect the trailing constraint to the outlet, letting me control where the trailing edge is of the subview.
To describe the closed and open states of the drawer, I have two methods. The closed drawer makes the drawer disappear by with an alpha of 0 and make the trailing constraint outside the visible view. I reverse the process for the open drawer. For a closed drawer I’ll dd this code:

drawer.alpha = 0.0
isOpen = false
drawerTrailingConstriant.constant = -(drawer.frame.width)

I’m going to add one more thing here. To animate a constraint you need to call for a layout change. You do that with the layoutIfNeeded method of UIView.

view.layoutIfNeeded()

To reverse the process, I’ll copy and paste this code in the openedDrawer, change the alpha to 1.0 the flag to true, and the constant to 0.
I made these methods so I can do both animated and non animated opening and closing. I’ll start the app with a closed drawer, so I’ll place in viewDidLoad

closedDrawer()

Finally, I’ll toggle between the closed and open drawer states. I’ll use the very flexible UIViewPropertyAnimator class for this. I already set up an if statement to decide when the drawer is open or closed.

if isOpen{

 } else {

}

Inside the true block, I’ll add:

let animation = UIViewPropertyAnimator(

There’s several animation options here. There are the standard animation curves like ease in, ease out, and linear. There’s also some customization options, which we’ll use here. For a drawer, and other UI moving on and off stage, you’ll find a spring based animation works well. That’s the one with a damping ratio. Damping ratios control how springy the end of the animation has. Add this:

let animation = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 0.2) 

In the closure, I’ll close the drawer

{
    self.closedDrawer()
}

I’ll add to start the animation

animation.startAnimation()

I’ll do the same for the opened drawer, so I’ll copy and paste this. However, I’ll give values that will give us the proper effect. Somewhere between 0.85 and 1.0 works well over one second.

let animation = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 0.85) {

In the closure I’ll change this to open.

   self.openedDrawer()
}
animation.startAnimation()

We are ready to run. Set the simulator to iPhone XR. Run the app.

Tap the options button and you’ll get the drawer opening. Tap again and the drawer will close, though very bouncy. Notice even the alpha is bouncing. Try some other values on your own. With this basic setup you can make many different styles of drawers with different personalities.

The Whole Code

Here’s the listing for this project. You can also download the project from GitHub here

//
//  ViewController.swift
//  AnimatedDrawer
//
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {
    private var isOpen = true //The state of the drawer
    @IBOutlet weak var drawerTrailingConstriant: NSLayoutConstraint!
    @IBOutlet weak var drawer: UIView!
    
    //toggle the state of the drawer
    @IBAction func openCloseDrawer(_ sender: UIButton) {
        if isOpen{
            let animation = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 0.2) {
                self.closedDrawer()
            }
            animation.startAnimation()
        }else{
            let animation = UIViewPropertyAnimator(duration: 1.0, dampingRatio: 0.85) {
                self.openedDrawer()
            }
            animation.startAnimation()
        }
    }
    
    // Code the state of the drawer here.
    func closedDrawer(){
        drawer.alpha = 0.0
        isOpen = false
        drawerTrailingConstriant.constant = -(drawer.frame.width)
        view.layoutIfNeeded()
    }
    
    func openedDrawer(){
        drawer.alpha = 1.0
        isOpen = true
        drawerTrailingConstriant.constant = 0
        view.layoutIfNeeded()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Start with a closed drawer
        closedDrawer()
    }


}

Tip: Add Drop Shadows

A video of this tip can be found on LinkedIn Learning

User Interfaces often use shadows to make them look less flat, but it isn’t obvious how to add one to a view on your app. In this tip, I’ll show you how to make shadows using the CALayer found in UIViews, and a few tricks with them.
I’ve set up a quick starter file with 2 buttons on the main story board you can download from the exercises
I’ve set up several actions for these buttons we’ll use in our demos as you’ll see here in the ViewController
All UIViews have a special property layer of type CALayer. The layer property is great for many quick special effects, some built-in. Drop shadows are one of those built-in effects.

You’ll find a basicDropShadow method in the code. It takes the button and adds drop shadow effects to it. There four basic properties to add. Add this:

button.layer.shadowOpacity = 0.7

The property shadowOpacity defaults to 0. The shadow is always there, but hidden. To show a shadow, change the opacity.
We can also change the radius of the shadow. This affects how blurry the shadow appears. Add this to basicDropShadow

shadowButton.layer.shadowRadius = 5.0

Where the shadow appears is set by the shadowoffset property of CALayer. Add this to basicDropShadow

shadowButton.layer.shadowOffset = CGSize(width: 5.0, height: 2.0) 

For offsets, positive numbers are down and to the right, negative numbers up and to the left.
Down in viewDidLoad add this method for the two buttons.

basicDropShadow(button: button1)
basicDropShadow(button: button2)

Build and run with an iPhone XR to get a pretty nice looking shadow.

Stop the app.

You’ll see I have two more actions available. Both are set to touch down events, one for each button. I wanted to show a few effects you can pull with a drop shadow using some of the others touch events on a button. One effect is to make the button look like it is physically pressed. When pressed, we move the shadow under the button.
In the button1TouchDownInside method add the following code.

sender.layer.shadowOffset = CGSize(width: 0, height: 0)
sender.layer.shadowRadius = 0.5
sender.titleLabel?.layer.shadowOffset = CGSize(width: -5, height: -2)

That will move the drop shadow from the lower left to the upper right, you’ll notice I reset the button to the basic drop shadow when we release the button.
Another trick with drop shadows is to make the button glow underneath when you have a touch down event. I’ll add one new property, shadow color and turn the shadow yellow.

sender.layer.shadowColor = UIColor.yellow.cgColor

This is a CGColor so I Make the UIColor and get the cgColor property for the CGColor. I’ll also make a bigger radius.

 
sender.layer.shadowRadius = 50

Finally to reset the shadow color, I’ll make the shadow black in the basic drop shadow

 
button.layer.shadowColor = UIColor.black.cgColor

Run this. Click button 1 and it appears to move down since the shadow underneath disappears. Click button 2, and when you press down it glows while the button is pressed down.

There’s a lot you can do with drop shadows. With these few simple properties you can add a whole new layer of cool to your apps.

The Whole Code

You can find this code for download here:

//
//  ViewController.swift
//  DropShadowDemo
//
//
//  A Demo for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    
    // Reset the button shadow 
    @IBAction func buttonTouchedUpInside(_ sender: UIButton) {
        basicDropShadow(button: sender)
    }
    //Pressed down, shadow hides under the button to give a pushing down effect
    @IBAction func button1TouchedDownInside(_ sender: UIButton) {
        sender.layer.shadowOffset = CGSize(width: 0, height: 0)
        sender.layer.shadowRadius = 0.5
    }
    //Pressed down, shadow 'glows' from teh center of the button
    @IBAction func button2TouchDownInside(_ sender: UIButton) {
       sender.layer.shadowColor = UIColor.yellow.cgColor
        sender.layer.shadowRadius = 50
    }

    // The basic shadow setup. 
    func basicDropShadow(button:UIButton){
        button.layer.shadowOpacity = 0.7
        button.layer.shadowRadius = 5.0
        button.layer.shadowOffset = CGSize(width: 5.0, height: 2.0)
        button.layer.shadowColor = UIColor.black.cgColor
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        view.backgroundColor = .lightGray
        basicDropShadow(button: button1)
        basicDropShadow(button: button2)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}