Unicode Characters in Strings

Special characters like emoji, accents, and symbols in your strings are easier to get than you think. This week, we’ll talk about how using Unicode characters in Swift Strings. Open the exercise file and you’ll find a project which we’ll use for this. I just hooked up a label to make a big display of what I want to show.

Hit Control-Command-Spacebar to get the character viewer. If you get a compact one like this, click the upper right corner icon to expand it.

When expanded, Click the settings gear in the corner . Select customize list.


There a big list of items. 

You can select languages, I’ll select the Enclosed Characters for example.

Go down to the bottom and open Code tables then add Unicode. Click Done.

Select Unicode in the character viewer.

The Unicode set gives you the power to add a lot to your strings. This table lists all the Unicode symbols, and activates Unicode labeling of symbols in the character viewer. In the search bar of the character viewer, find a doughnut. You see a Unicode identifier after it.

You can use these Unicode characters in your app. You’ll see the doughnut is U+1f369.

I can add the doughnut to my code as an escape sequence in the string of  \u{} 

yummy = "\u{1f369}"

run my app, And I get a doughnut. 

If I need an accented character, I can use a Unicode character. In the search window, I’ll search for n. In the related characters, I’ll find an ñ.

Click that

It has a unicode of U+00f1, so I can do this:

var yummy = "Bu\u{00f1}elos"

That’s not very flexible for multiple accent combinations. Instead of using the single character, you can use extended grapheme clusters, which combines two characters. 

Head to the Unicode section of the Character viewer. Find the n. which has a value of U+006e

There a special set for combining characters with diacritical marks starting at U+0300.

The combining tilde is at U+0303. I can change my string to this:

yummy = "Bu\u{006e}\u{0303}elos"

Run this.

The combining Unicode doesn’t need the Unicode base by the way, You can do this with an n too:

yummy = "Bun\u{0303}elos"

Also gets

Diacritical marks can be anywhere in the character space. I’ll head over to the Combining Diacritical Marks for Symbols.Click a few and you’ll see in the preview their location referenced by some guidelines.

Some are above, some below and some in the middle.  Comment out the yummy assignments  we have. I’ll demonstrate the positions with this string:

var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"

Run this and see all the fun. .  

There’s one thing you have to be careful of here. Extended grapheme clusters are counted as a single character in a character count. Change the code to 

label.text = yummy + String(format:" %i",yummy.count)

and run

Yields 9, ignoring the arrows. This makes sense if you are counting full characters, but it runs into problems for memory allocation. If you are bridging between NSString and Swift’s String type, the allocation is different due to the extended grapheme clusters. This is why Swift strings aren’t true arrays with an integer index like C-strings. The clusters make counts impossible. I can’t do this to get the third character.

let char = yummy[3] 

I’ll get an error

I can’t just use an integer subscript. Next week, I’ll show you how to handle character access and manipulation in strings. 

The Whole Code

Here’s teh code for this project. You can also download it here from GitHub

//
//  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

// an extra not found in the video:
// it doesn't hurt to make the characters constants or enums for more readable code.
    let upperArrow = "\u{20d7}"
    let lowerArrow = "\u{20ed}"
    let doughnut = "\u{1f369}"

class ViewController: UIViewController {
    var yummy = "D\u{1f369}ugh\u{20d7}n\u{20ed}uts"
    
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        yummy = "\u{1f369}"
        yummy = "Bun\u{0303}elos"
        yummy = "D" + doughnut + "ugh" + upperArrow + "n" + lowerArrow + "uts"
        label.text = yummy + String(format:" %i",yummy.count)
    }

}



Ranges in Swift

You’ve probably used ranges in loops, without knowing it, but ranges are really a type in Swift. Have you ever thought about ranges and all their power? I’ll show you a few things you might want to know about Swift ranges. I’ve put a playground into a Project for an exercise file. I added there an array to play with

Here’s the one place I’m sure you’ve seen a range: in a for loop. Commonly you’ll see them as a closed range using the ... operator, which include all values in the range. This code

Adds 0+1+2+3+4+5 to get 15

A half-open range takes one less than the number, by changing the last dot for a less than symbol(..<). This half-open example goes from 0 to 4. Which gives us a total of 10.

Open and close ranges are their own type Range, not to be confused with NSRange. I can assign a range like any other type. Note here the upper bound is outside the range. .

Then use that value as the range in my for loop.

You can also use a range directly in an array’s subscript. I can make a subarray of elements like this.

which makes a small array of the second, third, and fourth elements of the array.

You can use an half open range here too.   

You can stick a count for the array in the range, but there is a faster way. You can use fully open range. Open ranges don’t specify one bound of the range. For example to the end of the array like this: 

Or the beginning to iterate from the beginning to an end. 

You also can test for membership with the contains property. To see if my range contains 5 I’d use

Since I set range= 0..<5 this returns false as 5 is outside the range. If I look for 2

that returns true because 2 is between 1 and 4

the contains method become handy because you can use it to test if an index is within a range. For a simple example, I’ll write a snippet of code to check if an index is in a given array. I’ll check for element 4 and get Ice cream.

I check for 42, I’ll get nothing

I’ve stuck to Integers here, but ranges can be other types as well. Four example, you can add a range of Double, then check if a valur exists inthat range quickly.


One important caution I mentioned earlier: Unlike other Swift types, Range and NSRange is not the same thing.

NSRange is a location and a length , Range is upper and lower bounds. Closed ranges might be a bit easier to convert between the two, but open ranges make it very difficult to convert from one to another. Some API use Range and some NSRange. Make sure you know which one you are using.  

The Whole Code

You can also download this from Github here: If you copy and paste this into a Swift playground in iPad, the copy below will format itself.

import UIKit
//:# Range Demo
//
//:  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
//:

//: A swift playground formatted for iPad playgrounds

let myDesserts = ["Cannoli","Mochi","Bunelos","Pecan Pie","Ice Cream"]

//:## Basic Ranges
//: Closed range
var total = 0
for number in 0...5{
    total += number
}
total


//: Half Open range
total = 0
for number in 0..<5{
    total += number
}
total


var doubleRange = 0.1...6.7
doubleRange.contains(Double.pi)




//: Assigning ranges
var range = 0..<5
//:Range is not NSRange
var nsRange = NSRange(location: 0, length: 4)






total = 0
for number in range{
    total += number
}
total




//: Use as array subscripts
var eating  = myDesserts[2...4]
eating = myDesserts[2..<4]
eating = myDesserts[2...]
eating = myDesserts[...3]
//: Membership
range.contains(5)
range.contains(2)


let a = 42
range = 0..<myDesserts.count
var eat = ""
if range.contains(a){
    eat = myDesserts[a]
} else {
    eat = "nothing"
}
eat


Replace Segmented Controls with Button Arrays

You’ve probably used the Segmented control before like this one

It’s great for some simple uses but lacks flexibility. Besides using only text or single color icon, it doesn’t work in vertical or other arrangements. Let’s look at another solution: Using Button arrays. 

In the exercise files, I’ve set up for you a vertical stack view of buttons, though I could have placed these anywhere with Autolayout.

Open up the assistant editor. I’ve hooked up everything for you but the buttons.  Control-drag from the pizza button to the assistant editor.

While you could use the stack view’s arrangedSubviews array, a more flexible option is a collection of outlets with the outlet collection selection. Instaed of picking an outlet for the connection, select Outlet Collection

I’ll name the collection dessertSelection.

I’ll manually control drag the other buttons to this collection to keep the order correct.  

Control-drag again from the story board.  Make a action didSelectDessert with a sender of UIButton. Drag from the connector to the button so all buttons use the same action. 

Close up the assistant editor and head to ViewController. Each button now will have two states: selected and deselected. We’ll need to deselect everything, and then select the one the user tapped. 

@IBAction func didSelectDessert(_ sender: UIButton) {
     allDeselected()
     selectDessert(button: sender)
}

The power of that array shows up when deselecting the button. You can iterate through the buttons to shut them all off, which in this case is adding a drop shadow I made for you in the shadowOn method: 

func allDeselected(){
        for button in dessertSelection{
           shadowOn(button: button)
        }
    }

Selecting a dessert is doing the opposite to the selected button:

func selectDessert(button:UIButton){
        shadowOff(button: button)    
 }

This gives the selected button a pressed-down look. 

To finish the display, add an allDeselected to viewDidLoad to start fresh. 

override func viewDidLoad() {
        super.viewDidLoad()
        allDeselected()
    }

Run this, which I’ll do on a iPhone XR and you’ll  get buttons that select. For example select mochi

then select Napolean. Mochi pops back up and Napolean depresses.

Of course, you want it to select something.  To do that, make a selectedIndex function.  It will look for a selected condition in the array, which I’ll use the shadow radius, then return the index. 

func selectedIndex()->Int?{
        return dessertSelection.index(where: {(button)-> Bool in
               button.layer.shadowRadius == 1.0})
    }

Back in didSelectDessert, I’ll change the label’s text.  I’ll start by unwrapping the button’s title and the selected index. 

if let selection = selectedIndex() , let text = sender.titleLabel?.text {

}

I’ll set the text, adding 1 since users don’t think to start at zero.  

selectionLabel.text = "#\(selection + 1) " + text

 And finish with an else in case our index or title are missing. 

} else {
            selectionLabel.text = "No Selection"
        }

Run that, and try the buttons.

They now give the selection and the title of the button. This was a simple version of what you can do with an array of buttons. Unlike a segmented control you can arrange and format  them like buttons, which leads to a lot of flexibility in choice controls. 
 

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.