A common problem for people new to Auto Layout is dealing with UIScrollViews, because the constraints that are necessary to set up a scroll view and its contents aren’t always obvious.
When adding subviews to a scroll view, imagine that you’re adding views to a “content view” which the scroll view is a window into. The content view has no intrinsic size, but it needs to have a fixed size in order for the scroll view to work properly, so you will need to add constraints to give it that size.
If the view you’ll be adding to the content view does have an intrinsic size (an image view, for example), you can pin the edges of your subview to the edges of the content view to give the content view a fixed size. (Even though those constraints are technically between your content subviews and the scroll view, they will have no effect on the size of the scroll view.)
In cases where the content size of the scroll view actually should be affected by the size of the scroll view (a screen of text fields which scrolls vertically, for example), the simplest solution is often to make an extra view (a container view) which is used as the container for all the contents of the scroll view. A constraint can be added to force the width of the container view to be equal to the width of the scroll view. In fact, we do this so often that we‘ve added convenience methods to EPSUIFactory to create container views in code. For an example of how to set up your scroll view using storyboards, check out the example project.
For the last two years we have completely stopped using storyboards and xibs. We found that creating our layouts in code was both easier to maintain and saved us as well as our clients from fighting with the limitations of interface builder. With Xcode 6 and a significantly updated interface builder, this has changed (at least we think).
The thing that changed our mind was Live Views. Live Views allows you to use custom views in xibs and storyboards and actually see what you are doing. Previously if you were to create a custom view and add it to a view in a xib or storyboard the view would be white or clear and visually difficult to interact with. This causes significant problems when trying to layout the view with other views in your xib, etc. Now we can see the custom view, as well as adjust things in your view such as UIColors and UIImages and even the metrics dictionary for your auto layout code. The following is how you set this all up:
Step One: Setup Your View in Code
Setup a view in code and include the functions awakeFromNib and prepareForInterfaceBuilder. To make this process easier you will also want to create a function that sets up your layout. For the purpose of this blog post we will refer to this method as setupViews.
As you may have guessed, prepareForInterfaceBuilder only gets called when viewing your view in Interface Builder and not at run time. We will take advantage of this later on.
In order to turn your view into a Live View you must declare your view @IBDesignable before the class declaration.
Step Two: Setup Your View in Interface Builder
All you have to do now to see your view in Interface Builder is to call any methods setting up your views and layout in prepareForInterfaceBuilder. After that you have to use your UIView in your Interface Builder with your new custom view.
Step Three: Adjust Custom View from Interface Builder
In order to adjust elements of your custom view in storyboard you must declare them @IBInspectable like so:
This allows you to edit value of that variable in Interface Builder. In this example you would be given a color picker to choose an UIColor. Currently the following types support @IBInspectable.
I think there are lots of opportunities to do interesting things with @IBDesignable and @IBInspectable. Make sure to follow our blog, I will be posting some open source projects based on this post in the future.
Click here for an example project based on this blog post.
Dynamic Type allows applications to use fonts that vary in size based on a user’s preference. You can adjust the text size of your iOS device by going to Display & Brightness/Text Size in the Settings App (iOS 8 Beta 6). iOS 8 makes using Dynamic Type a lot easier to implement, and with a larger iPhone rumored to be on the way, it will most likely be more important than it has been in the past. Also, by adopting Dynamic Type, your application will respect the user’s preference for font size as well as making it easier for visually impaired users to use your application.
How to adopt Dynamic Type?
You can use Dynamic Type anywhere you would use a regular UIFont object. If you are using a label in storyboards or xibs you must set the font to one of the system text styles in order for Dynamic Type to work.
To use labels in code with Dynamic Type you simply have to set the font property as seen below to one of the supported text styles. (See the documentation.)
In order to implement Dynamic Type with custom fonts, you need to write a little logic. You should write a function which takes a UITextStyle and figures out what size your font should be based on the size of the system font for a specific text style. The function should also figure out what weight your font should be based on the text style. Make your function in an extension of UIFont and you will be able to use this just like preferredFontWithTextStyle(textStyle). See the example below:
Resizable Row Heights
If you use auto layout in a custom table view cell class, and the constraints in that cell make the height non-ambiguous, you can use Dynamic Type with the cell’s labels, combined with iOS 8’s new dynamic row height feature to implement a table view which uses Dynamic Type.
Responding to Text Size Changes
In order to make your application respond to a user changing the text size in system preferences, you need to write a little code. You must observe the UIContentSizeCategoryDidChangeNotification notification.
When you receive the notification you need to reset the fonts based on the text style.
To handle text size changes in custom cells, set the fonts every time layoutSubviews is called.
For-in-loops are often used to iterate through an array of objects, modifying another object with each iteration. For example, looping through an array of numbers in order to create a new array of numbers formatted as strings:
With Swift, iOS and Mac developers have map, filter, and reduce methods on arrays. In my experience, these methods can replace almost all uses of for-in-loops, and are more clear and concise.
The map method takes a function (transform), and returns an array containing the results of calling transform on each element in the array. The example above could be written like this:
The filter method takes a function (includeElement) which, given an element in the array, returns a Bool indicating whether the element should be included in the resulting array. For example, removing all the odd numbers from the numbers array could be done like this:
The reduce method reduces an array into a single value. It takes two parameters: a starting value, and a function which takes a running total and an element of the array as parameters, and returns a new running total. For example, getting the sum of all the numbers in an array could be done like this:
These three methods can be very powerful, especially when they are chained. For example, given an array of numbers, we can filter out all odd numbers, map the remaining numbers into strings using NSNumberFormatter, and reduce those strings into one string, with a formatted number on each line:
All the examples from this article can be downloaded as a playground from GitHub.
Continuing from my first post on Auto Layout, I'm going to use the custom avatar view that we made and lay it out in a window with two text fields:
When designing a UI, there is often one part of a view which should be the first to start shrinking if the view containing it changes size, or if there isn't enough room in the view to fit all the content. For example, in this window, I would like the text field on the right to always show its full content, and I would like the text field on the left to shrink if necessary.
Before we deal with which text field should shrink, lets set up the UI. We'll create an EPSAvatarView (from the previous post), and two NSTextFields, set up to act like labels. Here are the constraints we'll use to lay them out in the window:
Using, these constraints, at the smallest allowed window size everything is laid out correctly, but the window can grow taller than is necessary to fit the avatar view. This is because the avatar view's content hugging priority (the strength with which it resists being made bigger than its intrinsic content size) is too low. Since we want the avatar view to always be the exact size needed by the avatar, we will set its content compression priority to the highest possible priority, for its horizontal and vertical dimensions:
Once that's done, the window can no longer be resized vertically, but can still be resized horizontally. When it's larger than necessary horizontally, the layout system chooses (pretty much at random) which of the text fields expand in order to fulfill the constraints we've set up. To make the left text field always be the one which is stretched, we can raise the hugging priority of the right text field, to make it resist being sized bigger than its content:
With all these constraints set up, the window can only be resized horizontally, and the left text field will always be the view that is stretched to fulfill the constraints. The window also can't be sized smaller than the minimum size needed to fit all the content. If we want to make it possible for the window to be smaller than the content, and want the left text field to be truncated when that happens, we need to lower its compression resistance priority (the strength with which it resists being made smaller than its intrinsic content size):
Now the window can be sized much smaller—it still needs to fit the avatar view and the right text field, but the left text field can be shrunk to have a width of 0.
You can find the complete example code for this post on GitHub.
I cannot say it enough: Sketch by Bohemian Coding is my favorite design tool. I highly recommend using it for all of your iOS design work. I've made an iOS tab bar template so you can make tab bars really quickly for your apps. Feel free to change the colors, gradients and textures as you see fit for your project. Tweet me with your changes at @justinmstuart, I would love to check them out.
Auto Layout, introduced in OS X Lion, and later in iOS 6, had three WWDC sessions dedicated to it in 2012 (202, 228 and 232), but most of the time was spent in Interface Builder, laying out stock UI elements. I'm going to show you how to prepare a custom view for use with Auto Layout, done completely in code. The example will be an OS X app, but everything works almost identically on iOS. You will need to understand the basics of Auto Layout already to follow along with the example. The finished code can be found on GitHub.
We're going to make a subclass of NSView which takes an NSImage and draws it in a circle with a shadow underneath. The image, diameter of the circle, blur radius and blur offset of the shadow are all properties which can be set. It will end up looking something like this:
First, we need to override drawRect:. It's straightforward, and not related to Auto Layout, so I won't give any explanation.
Next, we need to override intrinsicContentSize. intrinsicContentSize returns the size of the view's content. In EPSAvatarView, since we don't want the shadow to be considered "content" (ie. taken into account when aligning other views), we will just return the size of the circular image.
We also need to override alignmentRectInsets. alignmentRectInsets lets you define the rect that should be used when aligning your custom view, which may be different than the actual frame of your view. For example, EPSAvatarView should be aligned using the circular avatar image, and the shadow should be disregarded. We return an NSEdgeInsets struct that gives the amount that should be inset from the edge of the frame for the top, left, bottom and right.
The layout system adds these insets to the intrinsicContentSize to calculate the smallest allowed frame of the view. You can see the difference between the rect used for alignment and the frame in this image:
In order to adjust intrinsicContentSize and alignmentRectInsets every time circleDiameter, blurRadius or blurOffset changes, we need to override all the setter methods and call [self invalidateIntrinsicContentSize]. We also need to call [self setNeedsDisplay:YES], to make sure the image is redrawn.
An example project with this file can be found on GitHub.
Sketch by Bohemian Coding is my favorite design tool. I use it for all of my iOS design work. I've made an iOS navigation bar template so you can make navigation bars really quickly for your apps. Feel free to change the colors, gradients and textures as you see fit for your project.
We use Gaug.es for our site analytics and Tender for managing our support queue, so I wrote a script that can get data from Gaug.es and Tender into Panic's new Status Board app for the iPad. You can find it on GitHub along with instructions.
Apple's App Store is now available in 155 countries. If your app is only available in English, there is a large untapped market of potential users who can't or won't buy your app if it's not available in their preferred language. The problem is that there are many different languages, calendars and currencies to account for around the world. For example, the USA, France, and Japan all have different formats for numbers, currencies and dates.
The solution is localizing and internationalizing your app. Localizing is the process of translating all of the strings in your app. Internationalization, on the other hand, is the process of making your app use the correct date format, units (metric/imperial), etc. based on the locale of the iOS device. This is a relatively simple process, and from my experience is worth the time.
Over 50% of our sales from Note-A-Lator and Fortepiano come from countries where English is not the primary language. Our non-English-speaking customers also tend to give feedback, suggestions and leave comments more then English speaking customers. I recommend localizing and internationalizing your app with the help of a professional translator such as Tethras. Professional translators don't cost as much as you might expect and accurate translations are a must when localizing an app. It goes without saying that Google Translate is not a viable alternative.
There is now an easy way to take screenshots in all of the languages you needs as well, thanks to Kent Sutherland of Flexibits. Check out his blog post. This saves an enormous amount of time since switching the language on your iOS simulator can be a pain.
Localizing and internationalizing your app can benefit your sales and is a relatively simple project worth considering.
In iOS 5 and OS X 10.7, Apple introduced a new Core Audio audio unit called AUSampler. It allows you to organize audio files as a playable instrument, useful for creating a in-app piano, for example. Unfortunately, as of February 2013, the only documentation consists of Session 411 from WWDC 2011, Technical Note TN2283 and some sample code. Instruments can be loaded from a file, which can be a DLS2 sound bank, SoundFont file, EXS24 file or an AUPreset file. In my case, I've always needed to create the instrument file myself, and Apple's suggested way of doing that is to use AU Lab, which can be downloaded as part of the Audio Tools for Xcode package from the iOS Dev Center.
The WWDC presentation shows how to create AUPreset files in AU Lab, but here are the basic steps:
Create a new document.
Choose "Add Audio Unit Instrument" from the "Edit" menu.
Set the instrument type to AUSample (defaults to DLSMusicDevice).
Click the + button to add a new audio file, and set the key range you would like it to be used for, as well as the correct root (the pitch the file is a sample of). (You will need to click on "Show Editor" to get to the + button.)
Add as many sound files as you need.
Delete the "Sine-440 Built-in" sample.
Choose "Save Preset As" from the preset drop-down menu (the third drop-down from the top left of the window) and save the preset.
Show all presets (⌘P), choose your preset, and click the "Show in Finder" button.
If you get error messages when you try to add new sound files to your instrument, make sure that a file with the same name doesn't already exist in your ~/Library/Audio/Sounds folder.
Using the Preset File
The preset file is created, but it and its corresponding audio files still need to be added to your Xcode project.
Add the preset file to your Xcode project.
Put all the sound files needed for the preset into a folder called Sounds.
Add the Sounds folder to your Xcode project, making sure to select "Create folder references for any added folders".
Explaining the code needed to set up the sampler is beyond the scope of this article, but the sample code and EPSSampler are pretty straightforward.
If you open your preset file, you'll see the that the audio files references use a full path, which will obviously only work on your computer. For example:
The AUSampler audio unit looks first for an audio file at the path given, and if nothing is found there, looks for the file in a Sounds folder in your app bundle, which is why we chose the "Create folder references for any added folders" option when adding the Sounds folder to your project.
This method of creating and using AUPreset files works, but is tedious and error-prone if you're trying to create a complex instrument. (For example, a piano instrument with samples for each pitch on the keyboard, as well as different velocity samples for each pitch, and separate samples for the release sounds of the keys would have two layers and hundreds of audio files.) Fortunately, it's possible to programmatically generate AUPreset files.
Generating AUPreset Files Programmatically
AUPreset files are really just .plist files with a different extension, so they can be opened in any text editor, or in the Plist editor in Xcode. Looking at the structure of the file, the important parts are the Zones in Instrument->Layers->first object, and the objects in file-references.
AUPreset File Structure
file-references is an array of dictionaries where the key is in the format Sample:### and the string value is the path to the sound file (eg. /Users/peter/Library/Audio/Sounds/C8.wav). The sample number always starts at 268435457 and increases by 1 for each entry in the array. For example, the first three file references might be:
Zones is an array of dictionaries which each represent a range of pitches which will use one particular audio file (pitch adjusted to the pitch being played). Here are the important keys and their corresponding values:
min key is the MIDI number of the lowest pitch in the range
max key is the number of the highest pitch in the range
root key is the MIDI number of the actual pitch of the audio file corresponding to the zone
waveform is the number representing the sample that is used in the file-references array
Generating the Preset with a Script
Since the AUPreset file format isn't documented and has some mysterious stuff in there (the data value, for example), the easiest way to generate one is to create the preset normally in AU Lab, but only add a couple of audio files, and use that as a template for the file you're going to generate. I've written a script which can generate a complete AUPreset file based on a template that you provide. You can download it and read more about it at the GitHub repository. It's used like this:
[starting pitch] and [ending pitch] are the MIDI numbers for the lowest and highest pitches, respectively. [template filename] is the path to the template file that you have prepared, and [output filename] is the path where you'd like the outputted preset file to be saved. For example:
To prepare your template file, take an existing AUPreset file and replace the contents of the Zones array with ***ZONES***. So this:
Also, replace all of the keys and strings in the file references dictionary with ***FILE REFERENCES***. So this:
Running generate_aupreset will output an AUPreset file with the necessary information filled in for your audio files. The script assumes that your audio files are named in the format [pitch][octave].wav, for example:
It will always use sharps (#) instead of flats (♭) in the filenames. (ie. A# instead of B♭, C# instead of D♭, etc.) Once your final AUPreset has been generated, you should be able to add it and the audio files to your Xcode project as outlined above in "Using the Preset File".
Currently the script only works with single-layer AUPreset files, but it could easily be improved to deal with more complicated files. (Contributions are welcome!)
The script makes it relatively easy to generate AUPreset files on your computer, for use with audio files shipped with your app, but what if you want to generate the preset on an iOS device, using sounds provided by the user, or downloaded as part of an in-app purchase?
Generating the Preset on a Device
The file references in AUPreset files require either a full path to the audio files, or for the audio files to be in a Sounds folder in the app bundle, the NSLibraryDirectory, the NSDocumentDirectory or the NSDownloadsDirectory. Basically, this means that you can use any path you want in the AUPreset file, as long as the actual audio files are stored in one of those folders. That makes it possible to use audio files created on the device, or downloaded by the app, as long as they are saved to an appropriate location.
We are demoing Busboy at Drinks On Tap this Wednesday (2/13/13). Come and check us out, it should be a fun evening of demos, drinks and good conversation about mobile development. RSVP today and we will see you there!