Wednesday, May 18, 2016

NSRegularExpression... String? NSRange? Wha...? (Swift 2.2)

Trying to work with NSRegularExpression in Swift, I came across this common issue: to get matches, you must provide a Swift String to match on. However, NSRegularExpression returns NSRange objects representing any resulting matches. Problem is, String and NSRange don't play well with each other!

To work around this, as usual, I'll give you the short of the story, followed by the details.

The Short Story:
Convert your resulting NSRange match items into Range items with the original string's String.UTF16View representation. UTF16View has a samePositionIn(String) function ready-made for this purpose!

Example code:

// Note: all types are specified for clarity //
// set up the regular match String AND a UTF-16 version
let matchString: String = "This is the text to search with the Regular Expression pattern."
let matchStringUTF16: String.UTF16View = matchString.utf16
// create your NSRegularExpression object
// get your match(es) - which are NSRange objects -
// and while looping through each:
// use the NSRange's location and length to create start and end values for the String Range
// (note: 'match' represents the current NSRange object; 'idx' is the current index of the loop)
let theNSRange: NSRange = match.rangeAtIndex(idx)
let nsRangeStart: Int = theNSRange.location
let nsRangeEnd: Int = theNSRange.location + theNSRange.length
// create UTF-16 start and end indexes using the UTFView version of the original String
let utf16StartIndex: String.UTF16View.Index = matchString16.startIndex.advancedBy(nsRangeStart)
let utf16EndIndex: String.UTF16View.Index = matchString16.startIndex.advancedBy(nsRangeEnd)
// use samePositionIn(String) to convert the UTFView indexes to regular CharacterView indexes
// note: these are optionals as the conversion may not work; handle appropriately in your own code
let stringStartIndex: String.CharacterView.Index? = utf16RangeStart.samePositionIn(matchString)
let stringEndIndex: String.CharacterView.Index? = utf16RangeEnd.samePositionIn(matchString)
// now, create a Range object with those CharacterView indexes
// FYI: Range<Index>, as seen in error messages, is short-hand for Range<String.CharacterView.Index>
let stringRange: Range<String.CharacterView.Index> = stringStartIndex!..<stringEndIndex!
// and prove it works by using the new Range on the original String to get the match's substring
let substringMatch: String = matchString.substringWithRange(stringRange)

Here are the details:
One example NSRegularExpression object method call looks like this:

func matchesInString(_ string: String,
options options: NSMatchingOptions,
range range: NSRange) -> [NSTextCheckingResult]

It requires a String for matching, but an NSRange for the part of the string to search. To convert TO an NSRange, you need to convert your regular String into an NSString to get the length - this part's easy:

// we're assuming we want to search the entire string
let nsMatchString: NSString = matchString as NSString
let nsRange: NSRange = NSMakeRange(0, nsMatchString.length)

Ah, but WHY? Why didn't we just use matchString.characters.count?

A Story of Character Encoding
Well, it turns out that OS X and iOS use an underlying character encoding called Unicode. Unicode utilizes "code units" to make up strings. And, the number of code units representing a given character will differ depending on how the character is represented in the UTF standard.

NSString uses a UTF-16 representation for working with Unicode characters. While many characters consist of only one code unit in UTF-16 form, some characters consist of more than one code unit. A letter "A", for example takes only one code unit. But, the emoji {Smiling face}, "☺️", consists of 2 code units in UTF-16 (and therefore also NSString).

Swift's String, on the other hand, uses a visible character count rather than calculating the underlying code units. So, the String literals "ABC" and "A☺️C" both seem to be 3 characters long: "ABC".characters.count and "A☺️C".characters.count both return 3.

But, when you convert those same strings to NSString, you get a little surprise: ("ABC" as NSString).length returns the 3 you'd expect. ("A☺️C" as NSString).length, though, returns 4! Yike!

Of course, the difference is because NSString is taking into account that extra UTF-16 code unit that String ignores.

Okay, now what about those ranges?
Swift bridges the String you provide to the function into an NSString object. So, the NSRange results you get back from the call are based on that UTF-16 length, not the actual String character count. If you've got any multi-code-unit characters in your String, the ranges you get back won't match up.

Additionally, NSRange objects provide a location and a length. These are both Int values, which won't work in a Swift Range. You need Index objects for the start and end of a Range object.

Speaking of indexes, Swift Range objects expect a particular type of Index. You've seen the error messages about Range<Index> or Range<String.Index>. These actually represent Range<String.CharacterView.Index>. So, you need to make sure you're using the right type of Index, or your conversion won't work.

The workaround!
Note: in the example code below, I haven't provided any error handling. Make sure to add code to handle any potential optional results, such as in creating your NSRegularExpression object. Guard statements come in handy here.

Now, here we go...
1) Swift has some UTF-16 representations for a String object. So, first, we'll ensure we've got a UTF-16 version of our match string. We'll use the utf16 property of String to convert our match string into its equivalent String.UTF16View type as well:

// set up the regular match String AND a UTF-16 version
//let matchString: String = "ABCABC" // switch the remarked string to experiment
let matchString: String = "A😏CA😏C"
let matchStringUTF16: String.UTF16View = matchString.utf16

2) We then set up our NSRegularExpression object.

//let regexPattern: String = "A(BC)+" // again, switch remarked strings to experiment
let regexPattern = "A(😏C)+"
let options = NSRegularExpressionOptions.CaseInsensitive
guard let regex = try? NSRegularExpression(pattern: regexPattern, options: options) else {
return
}

3) Then, we make our call to the regex object's appropriate function using the String value and the NSRange based on the NSString representation.

Note: strangely, the UTF16View version of the match string won't work for creating the NSRange. You MUST use an NSString cast to create the NSRange.

// creating the NSRange object to send into the function call
let nsMatchString: NSString = matchString as NSString
let nsRange: NSRange = NSMakeRange(0, nsMatchString.length)
// function call with original String and our new NSRange
let matches: NSTextCheckingResult? = regex?.matchesInString(matchString, options: matchingOptions, range: nsRange)

4) You'll need to loop through the results, converting as you go:

for (idx, matchItem) in matches.enumerate() {
let numMatches = matchItem.numberOfRanges
// now THIS gives us the NSRange for the main match PLUS each of the group captures
// rangeAtIndex(0) = main match (same object as matchItem[idx].range)
// rangeAtIndex(>0) = group capture match
for matchIdx in 0..<numMatches {
// use the NSRange's location and length to create start and end values for the String Range
// (note: 'match' represents the current NSRange object; 'idx' is the current index of the loop)
let theNSRange: NSRange = match.rangeAtIndex(idx)
let nsRangeStart: Int = theNSRange.location
let nsRangeEnd: Int = theNSRange.location + theNSRange.length
// then, using the UTF16View string, get the start and end UTF16 index types
let utf16StartIndex: String.UTF16View.Index = matchString16.startIndex.advancedBy(locationNS)
let utf16EndIndex: String.UTF16View.Index = matchString16.startIndex.advancedBy(locationNS + lengthNS)
// using the String.UTF16View.Index type's samePositionIn(String) function,
// you can finally get the String.CharacterView.Index types needed for the Range<String.Index>
let stringStartIndex: String.CharacterView.Index? = utf16StartIndex.samePositionIn(matchString)
let stringEndIndex: String.CharacterView.Index? = utf16EndIndex.samePositionIn(matchString)
// create your Range
let stringRange: Range<String.CharacterView.Index> = stringStartIndex!..<stringEndIndex!
// and use it against the original String to get your matching String - PHEW! All done!
let substringMatch: String = matchString.substringWithRange(stringRange)
}
}

Friday, April 22, 2016

Variadic Parameters (or, what are those three dots after the parameter type?) (Swift 2.2)

Swift gives us a cool way to indicate a variable number of parameters of a specified type in a function signature. This is called "variadic parameters". The parameter list is automatically converted to an array under the hood.

Note: A variadic argument can consist of zero or more values. As a result, the array could be empty. Make sure to account for this in your code, if necessary!

Here's what it looks like:
func doSomethingWithTheseColors(colors: UIColor...) { 
    // treat the list of colors as an array, eg:
    for (theIndex, theColor) in colors.enumerate() {
        print("\(theIndex). \(theColor.description)")
    } 
}
This function can then be called like this:
doSomethingWithTheseColors()
or
doSomethingWithTheseColors(UIColor.redColor)
or
doSomethingWithTheseColors(UIColor.redColor, UIColor.greenColor, UIColor.blueColor) 
You can call the function with as many arguments as you want (of the specified type, of course) - or none at all.

Resource: The Swift Programming Language (Swift 2.2) -> Functions (See Variadic Parameters)

Thursday, April 14, 2016

Adventures installing GnuPG (GPG) on El Capitan (Mac OS X 10.11)

GitHub has announced that they'll start marking commits and tags signed with GPG (GnuGP) signatures with a Verified tag. Excited about the thought of verifying my identity as to adds and updates, I went straightforward to set this up!

Note: a Mac binary (DMG) exists for setting up a suite of GnuGP tools: GPGTools
Also, a Mac binary exists for the "modern" version (meaning the up and coming, not yet stable version): GnuGP for OSX

I recommend one of these if you want a quick, easy setup on your Mac. However, I have to do things the hard way (mocking grin here ;)

Seriously,
- I only wanted the encryption/signing capability, as opposed to the whole email thing (GPGTools), and
- the GPGTools site was last updated in November of 2015 and indicated their El Capitan version was still in beta, while the GnuGP for OSX was the newest, not-"stable" version, and
- REALLY, I like to learn new things and understand how the underpinnings work. This was a chance for me to try "make"ing something from source on my Mac beyond simple C programs. I've generally used various tools that handle that stuff under the hood, so I thought this should be interesting.

Following the instructions here, then, I began the install process.

Long story short, including lessons learned:
  1. Download the following from the GnuPG Download page:
    • GnuPG stable
    • Libgpg-error
    • Libgcrypt
    • Libksba
    • Libassuan
    • Pinentry
  2. Download Gnu Portable Threads (Gnu pth). (Note: there may be newer versions of this or alternate configurations. I used v2.0.7 as linked on the page.)
  3. Verify each package's authenticity (please, no discussions of trust here! It is what it is.)
    • For Mac, use 'openssl sha1' to verify that the signatures match.
    • Text signatures for the GnuPG-listed packages are on at: GnuPG Integrity Check.
    • Gnu pth provides a .sig file for verification. To use this, you must already have a prior version of GnuPG installed. Alternatively, I found a matching SHA1 hash at the Free Software Foundation's membercard page for pth-2.0.7-14:  9a71915c89ff2414de69fe104ae1016d513afeee
  4. Uncompress all of the packages.
  5. Saving GnuPG itself for last, run these commands in Terminal from each package's own folder:
    • ./configure
    • make
    • sudo make install

Following these steps, the error you're most likely to find on a Mac has to do with unknown type names (intmax_t and uintmax_t). This error and the fix I chose, for right or wrong, is described in the long story below.

Once installed, follow the steps at GitHub Help for GPG to set up your account to use verification.

Setting up for GitHub, I discovered a few more things to note:
- If you're using a private email for GitHub, set up your GPG key with that email, not your "real" one.
- If your GPG installation uses the 'gpg2' command instead of just 'gpg', see the fix described in Edit 1, below.
- If your 'pinentry-agent' gives you grief about an 'inappropriate ioctrl', see the fix in Edit 2, below.


The long story:

I started by downloading GnuPG stable and verifying its authenticity:
- run the following command in Terminal to verify the signature:
openssl sha1 path/to/downloaded/file

- then compare the text signature from the GnuPG Integrity Check page with the result.

I then double-clicked the compressed file to break out the enclosed folder,

In Terminal, I cd'd (is that a word?) to the folder (gnupg-2.0.30 in my case) and typed the command ./configure (note: this is dot slash configure). The end output was a list of four dependencies I needed to install: libgpg-error, libgcrypt, libassuan, and gnu-pth.

Starting with Gnu Portable Threads (pth) (the messages said to install that one first!), I followed these steps for each dependency:
- Download the compressed file
- Locate and match a text version of the appropriate signature (using openssl as above)
- Uncompress the file (double-click in Finder)
- cd to the resulting folder, and
- run the following commands:

  1. ./configure
  2. make
  3. sudo make install

Note: no dependencies for 'libkbsa' or 'pinentry' showed up on the original ./configure list. However, I recommend you install those here as well in order to avoid pain later on ;)

Everything went smoothly, until I was ready to continue with GPG. I ran './configure' again, which appeared to complete with no problems. When I ran 'make', though... a bunch of errors came up having to do with 'intmax_t' and 'uintmax_t'.

intmax_t/uintmax_t errors:

The error messages looked like this, with a few pointing to 'uintmax_t' as well:

/usr/include/inttypes.h:235:8: error: unknown type name 'intmax_t'
extern intmax_t
       ^                                                                                                   

So, I went a'hunting. I found several resources which helped me locate and fix the problem, enabling me to complete the installation.

If you've worked with C, Objective-C, or C++, you know that you have to "include" header files for various reasons. Of course, you don't want to duplicate header file inclusions - and, you don't generally need to do so. Once you have included a header in one file, if you include that file in another file, the original header comes along for the ride.

Well, the problem here is that GnuPG has files designed to work on multiple systems, without knowing exactly what those systems will look like. And, apparently, the GnuPG developers generally work with Linux - not Mac. So, they do their best to code stuff that'll work on the Mac, but... well, there's reason they point you to the Mac-related GPG sites listed at the beginning of this article.

Getting to the point, a Mac with Xcode installed has two 'stdint.h' files and two 'inttypes.h' files. To further the complication, GnuPG has some 'stdint.h' files of its own.

Xcode's package contents contain a 'stdint.h' file. This file uses a typedef statement to define the constants 'intmax_t' and 'uintmax_t'.

Xcode's package contents also contain an 'inttypes.h' file. This file has no reference to either 'stdint.h' or 'intmax_t'/'uintmax_t'. However, it does have a reference to another 'inttypes.h' file, via a #include_next statement.

Now, #include_next means that the next file of the specified name found in the header search path should be included. Yike! Who knows where that could be (from strictly a file perspective, anyway) if someone might've changed up the search path?! Wow!

In this case, it appears that the next 'inttypes.h' file is in the /usr/include/ directory. At least, that's the file that's giving us those "unknown type name" errors.

The weird thing is that the /usr/include/inttypes.h file does #include a 'stdint.h' header. Theoretically, that header should contain the 'intmax_t' and 'uintmax_t' definitions.

We know that Xcode's package contents version has a typedef statement to handle that, so I checked the /usr/include/stdint.h: it has a #include <_types/_intmax_t> statement, as well as a corresponding statement for 'uintmax_t'. So, that 'stdint.h' should work, too. However, the /usr/include/inttypes.h file doesn't seem to be using either of those 'stdint.h' files. So, what 'stdint.h' is actually getting included?

It turns out that GnuPG has a 'stdint.h' file in the 'gl' directory that is causing - or related to, anyway - the problem. To fix the issue, locate that gl/stdint.h file under your GnuPG stable's folder.

Open the gl/stdint.h file in an editor (I suggest making a backup copy first, of course!), and look for this line:

# include "///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.3.0/include/stdint.h"

Remark out that line (put /* */ type comment marks around it) or delete it, and replace it with:

# include "///usr/include/stdint.h"

Now, try your 'make' command again. This time, assuming no missing files (such as libkbsa or pinentry), everything should work!

Continue with the 'sudo make install' command, and you should be ready to go!

Note: most of the GnuPG documentation uses the command as 'gpg'. For the version I used (2.0.7), the command is actually 'gpg2'. In this case, you may need to make various tweaks to get the GPG to run correctly with whatever application you're using. I'll leave that research up to you.

Enjoy your new certificate/key signing capabilities!


Edit 1:
When I tried to use my GnuPG (v2.0.7) with Git, I found Git didn't like the 'gpg2' command. Per a Stack Overflow answer provided by Jens Erat, I was able to use the following command to fix this issue:

git config --global gpg.program gpg2


Edit 2:
When I tried to perform a secure commit in Git, I got an error from the gpg-agent: inappropriate ioctl for device. Pinentry needs access to the Terminal window in order to allow you to enter the passphrase for your commit key. This error means that pinentry can't access the Terminal window correctly.

To fix this, create a .bash_profile file under your user's home (~) folder. (Note that the filename starts with a dot.) Add two lines to this file:

export GPG_TTY=$(tty)
export PINENTRY_USER_DATA="USE_CURSES=1"

Save the file, then restart your Terminal session.

To check if the change is in effect, run the 'set' command at the Terminal prompt. In the list of environment variables this command returns, look for entries for GPG_TTY and PINENTRY_USER_DATA. Ensure their values appear correct: the 'tty' value from the profile will be replaced by a default Mac value, while the pinentry value will show as expected.

Now, you'll be prompted for the passphrase so your commit will be signed properly.

Again, I am using OS X 10.11 El Capitan. This, or some similar setup, may work in other versions of OS X and on Linux, based on my research, but tread on at your own risk. Also, I'm using the default Bash shell.












Friday, March 25, 2016

Even more easily use a Navigation Bar without a corresponding Navigation Controller!

Well, so I wrote up this set of steps to add a navigation bar independent of a navigation controller:
Easily use a Navigation Bar without a corresponding Navigation Controller

Only to find that there's an even EASIER way!

Posted by joerick on Stack Overflow, you can do this in 3 steps - completely within the storyboard:

1) Add your navigation bar.

2) Add your constraints:
  • Navigation Bar.Top = Top Layout Guide.Bottom
  • Navigation Bar.Leading = Superview.Leading
  • Navigation Bar.Trailing = Superview.Trailing

3) In the Identity Inspector for the bar, add a user defined attribute:
  • Key Path: barPosition
  • Type: Number
  • Value: 3

That's it!

Many thanks to joerick! If this helps you, give 'em a vote up:
ios - Is there a way to change the height of a UINavigationBar in Storyboard without using a UINavigationController? - Stack Overflow


Again, here are some GIFs to show you the details:

1) Add your navigation bar. Just center it and click it to the guide. Don't worry about the weird space at the top.





2) Set the constraints for the navigation bar in the storyboard. All should have a constant of zero and a multiplier of one. Note: Ctrl-click to connect the navigation bar with the view in the outline; shift-click to select multiple items in the list:
    - Navigation Bar.Top = Top Layout Guide.Bottom
    - Navigation Bar.Leading = Superview.Leading
    - Navigation Bar.Trailing = Superview.Trailing




3) In the Identity Inspector for the bar, add a user defined attribute for barPosition, give it a type of Number, and give it a value of 3.






4) As you can see, you still get the same result - a normal, autoresizing navigation bar!







Thursday, March 24, 2016

Easily use a Navigation Bar without a corresponding Navigation Controller

Recently I had to wrangle with an independent navigation bar, without a corresponding navigation controller. My challenge was to get the bar to resize appropriately, as it would normally do if it had a navigation controller - and do it primarily via the storyboard.

It turned out to be surprisingly easy!

Here's the short version:
1) Add the navigation bar to the view controller in the storyboard.

2) Set the constraints for the navigation bar in the storyboard:
    - Navigation Bar.Top = Top Layout Guide.Bottom
    - Navigation Bar.Leading = Superview.Leading
    - Navigation Bar.Trailing = Superview.Trailing

3) Connect the storyboard's navigation bar to its view controller as an @IBOutlet.

4) In the view controller's class, add a UINavigationBarDelegate protocol to the class declaration.

5) In the view controller's viewDidLoad method, set the navigation bar's delegate to self.

6) In the view controller, add the bar positioning delegate's positionForBar method. Return the UIBarPosition representing TopAttached. (Note: UINavigationBarDelegate inherits this method from UIBarPositioningDelegate.)

That's it! Now your navigation bar will autoresize as though it were being controlled by an actual navigation controller - without the navigation controller!

Here's the long story:
1) Add the navigation bar to the view controller in the storyboard. Note that the navigation bar doesn't go all the way to the top! Line it up below the battery icon, where the guide "snaps" it in. This looks weird, but trust me, this'll work fine by the time we're done.


















2) Set the constraints for the navigation bar in the storyboard. Note: Ctrl-click to connect the navigation bar with the view in the outline; shift-click to select multiple items in the list:
    - Navigation Bar.Top = Top Layout Guide.Bottom
    - Navigation Bar.Leading = Superview.Leading
    - Navigation Bar.Trailing = Superview.Trailing




3) Connect the storyboard's navigation bar to its view controller as an @IBOutlet.

























4) In the view controller's class, add a UINavigationBarDelegate protocol to the class declaration. (Note: this protocol inherits from the UIBarPositioningDelegate protocol, allowing us to use the positionBar method in step 6, below.)









5) In the view controller's viewDidLoad method, set the navigation bar's delegate to self. (Note: we can do this since we set the view controller up as a UINavigationBarDelegate in step 4, above.)




6) In the view controller, add the bar positioning delegate's positionForBar method. Return the UIBarPosition representing TopAttached.  (Note: as mentioned in step 4, above, this method is available to our view controller because UINavigationBarDelegate inherits from UIBarPositioningDelegate.)




7) Try flipping back and forth in the simulator (or on a phone)! Notice how the navigation bar is correctly resized and positioned - without any further action on your part!

Thursday, March 17, 2016

Segue Unwind to One of Two View Controllers (Swift 2.1)

Situation: I have a table view and a collection view. Both can go to the same view controller for editing an item. Now, how to get back to the correct starting view?

Resources I used:
Technical Note TN2298: Using Unwind Segues
Specifying the Destination of an Unwind Segue Programmatically
View Controller Programming Guide for iPhoneOS: UsingSegues

How I did it:
- Add an instance variable to both the table and collection view controllers. We'll use this to tell whether the containing controller started the segue. Default it to false, of course.
var startedEditorSegue = false

- Override the prepareForSegue function for both view controllers. Here, we'll set the startedEditorSegue variable to true, since we're starting the segue from the specified controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let segueId = segue.identifier else {
return
}
switch segueId {
case "tableViewSegueToEditor":
  // this controller is starting a segue to the editor, so unwind needs to return here
startedEditorSegue = true
default:
print("not editor segue")
}
}

Note: I don't have any untitled segues that should be coming through, so I used a guard statement. If you have untitled segues, you may want to use conditional unwrapping (if let segueId = segue.identifier...) or something else instead.


- Override each of the controller's canPerformUnwindSegueAction functions. If the controller started the segue, it will return true due to prepareForSegue code above. If not, the controller will return false.
override func canPerformUnwindSegueAction(action: Selector, fromViewController: UIViewController, withSender sender: AnyObject) -> Bool {
 // if we started the segue, then we can handle it; otherwise, pass
return startedEditorSegue
}
Note: if you need to do multiple unwind actions, you can check which one is occurring via the action parameter. In this case, the parameter will contain my custom selector: unwindFromEditor:


- Create a custom unwind function with the same name in both the table and collection view controllers. Here, we'll reset the startedEditorSegue to false, so it'll be ready for the next round.
@IBAction func unwindFromEditor(segue: UIStoryboardSegue) {

 // the editor's unwind came here; all we need do is revert the indicator
 // to false, so it's valid for the next unwind action
startedEditorSegue = false
}
Note:
* This action doesn't get "connected" to anything, but must be an @IBAction anyway.
* The name of the function can be anything you want.
* The function must have one parameter of type UIStoryboardSegue.


- In the storyboard, CTRL-drag from the editor view controller's button to initiate the segue to the same view controller's exit icon. You'll get a menu with your custom-named unwind action.



Select the action - and you're done!

Now, when the appropriate button is pressed, the unwind segue will traverse your view hierarchy. It'll ask each view whether it can respond to the unwind. Due to the canPerformUnwindSegue function in conjunction with the startedEditorSegue variable, the table and collection view will respond appropriately, and the process will return to the right place.

On a side note: you can give the segue an identifier: select the segue in the Storyboard's document outline, and open its Attributes Inspector.

Happy coding!



Tuesday, March 8, 2016

Dang those broken constraints (because of the status bar!)

I keep looking this up, so here I'm putting it for posterity:

This Xcode error:

<NSLayoutConstraint:0x134682ba0 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x13468f180]   (Names: '|':UITextEffectsWindow:0x13468df70 )>"
Means:
    NOTHING! Well, actually, it means simply that the status bar is not in its normal state.

Explanation:
    In certain cases, your iPhone's status bar will be extended. For example, if you're in a call and navigate out of the main call window, a green bar representing the call will appear above the normal status bar area. Another example: if you're using the Personal Hotspot on your iPhone, a blue bar showing the connection status appears above the normal status bar area.

    If you're testing an app while this extended status bar is present, you will get the crazy NSLayoutConstraint/UIInputWindowController-top/UIInputSetContainerView/UITextEffectsWindow error message.

That's all it is, folks! Nothing to worry about at all!

Sunday, February 14, 2016

How do I set up sound effects in iOS (Swift 2.1)? (Or, man! What is it with this digital sound stuff?!)

So, awhile back, I took a digital music programming class for a language called ChucK. OMG! I found out that I know NOTHING about music! (5th - 8th grade clarinet in band, I thought a knew a little - but I was wrong! ;)
Now comes an opportunity to code some sound effects in iOS - and I'm still struggling with the concepts of digital/electronic music. What's a “mixer”? Do I need one? What is an audio node, an audio unit? What exactly is “reverb” and “delay”? Which pieces do I need and how do I put them together to make an “echo” or change the pitch? Well, I still don't know exactly, but I can tell you enough to get some interesting sound effects going in your Swift 2.1 iOS app. Read on...
In ChucK, you “ChucK” stuff to the DAC to make sound come out. Really? It looks something like this:
Gain masterGainLeft => Pan2 panLeft => dac.left;
Gain masterGainRight => Pan2 panRight => dac.right;
Gain masterGainCenter => Pan2 panCenter => dac;
The => is the “ChucK” symbol. In this example, I'm taking variables of type Gain named “masterGain”-something, “ChucKing” them to Pan2 objects named “pan”-something, which are each then getting “ChucKed" to one of the DAC's left, right, and center nodes.
If you want to know what this stuff means, see the footnotes at the bottom. For now, just know that they're all particular facets of an audio ”sound chain”.
I was able to understand enough of the concepts to make music to complete the ChucK course. But, now I needed to do it again in Swift…
As I tried to "gain” (sorry! ;) a better understanding of how the pieces fit together, this video helped:
AVAudio in Practice - WWDC 2014, Session 502
In short, my Swift “sound chain” needed something like this ChucK statement:
input => effect => DAC

To do this in Swift at a most basic level, I followed these steps:
1. Create an AVAudioEngine.
2. Create an AVAudioPlayerNode, and attach it to the audio engine.
3. Set up one or more effects. (Some example effects are: AVAudioUnitTimePitchAVAudioUnitDelay, and AVAudioUnitReverb.) Set any values associated with the effects (such as pitch or ”wet/dry mix"), then attach the effect(s) to the audio engine.
4. Connect the pieces of the sound chain together using the audio engine's connect function, starting with the AVAudioPlayerNode and ending with the AVAudioEngine.outputNode (representing the DAC in iOS).
5. Using the audio player node's scheduleFile function, set the chain up to play.
6. Start the engine.
7. Using the audio player node, play the sound.
Here's what that might look like in code for a pitch change:
/**
    Plays audio at specified pitch.

    - Parameter pitchVal: Pitch at which to play audio.
*/
func playAudioAtPitch(pitchVal:Float) {
    // the audio engine is a global variable,
    //  instantiated in viewDidLoad
    // my resetAudio function stops and resets
    //   the engine before we set up our sound chain
    resetAudio()

    // set up audio player node, and attach it to the engine
    // (the audio player node is also a global variable)
    let audioPlayerNode = AVAudioPlayerNode()
    audioEngine.attachNode(audioPlayerNode)

    // set up pitch effect node, and attach it to the engine
    let pitchEffect = AVAudioUnitTimePitch()
    pitchEffect.pitch = pitchVal
    audioEngine.attachNode(pitchEffect)

    // connect the nodes to each other through
    //  the audio engine to make the sound chain:
    // AVAudioPlayerNode => AVAudioUnitTimePitch
    // AVAudioUnitTimePitch => AVAudioEngine's output node
    audioEngine.connect(audioPlayerNode, to: pitchEffect, format: nil)
    audioEngine.connect(pitchEffect, to: audioEngine.outputNode, format: nil)

    // schedule the recording to play
    //  audioFile is a global AVAudioFile variable,
    //    instantiated in viewDidLoad with a sound file
    audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: nil)

    // start the engine
    try! audioEngine.start()

    // play the audio
    audioPlayerNode.play()

}
Easy peasy, eh? It plays the sound file with the pitch effect. Here's more detail on how this works...
You’ve got a sound file you want to play with effects. You set up an audio player node to be the “input”, representing your sound file. That input will be put through whatever effects you set up. Then, the whole thing will be put out through the DAC, er, audio engine’s output node. (BTW, the output node points to the default sound output for your device.)
I assume you get pitch. What about “reverb” and “delay”? What are those? You can see a nice GIF and description here that can give you a head start:
Reflection: Echo vs Reverberation
Basically, reverberation is like a much smaller echo. Reverberation is what happens in a room (or singing in your shower ;) while echo is what happens when you yell around a bunch of rock walls, such as in a canyon. This is where “delay” comes in. Take a reverb, add a delay - and you’ve got an echo.
So, we can do this with the code above. We’ve got a pitch node, so let’s add delay and reverb nodes, using the same pattern we used for the pitch effect:
    // set up delay node for echo
    let delayUnit = AVAudioUnitDelay()
    delayUnit.wetDryMix = echoWetDryMix
    audioEngine.attachNode(delayUnit)

    // set up reverb effect node
    let reverbEffect = AVAudioUnitReverb()
    reverbEffect.loadFactoryPreset(.Cathedral)
    reverbEffect.wetDryMix = reverbWetDryMix
    audioEngine.attachNode(reverbEffect)
Er… what is this wetDryMix function? Numerous explanations exist on the web; maybe you’ll be able to find one that makes sense to you (unless you work with Midi equipment or the like and totally get it already!). The value is a Float, representing a percentage of the original sound vs the effect’ed sound. A value of 0.0 will give you no effect, while a value of, say, 2.0 might give you a humongous effect. (Check Apple’s documentation for valid value ranges.)
I set my echoWetDryMix to 0.0 and my reverbWetDryMix to 25.0 to get a lovely cathedral sound. This provided all reverb, no echo. Alternatively, I set my echoWetDryMix to 10.0 and my reverbWetDryMix to 0.0 to get an awesome echo, with no reverb. Experiment with your values to see what interesting things happen!
Now that we’ve added the delay and reverb effects, we need to adjust our sound chain to include them. It should look something like this:

input => pitch => delay => reverb => output
So, in Swift, change your connections section to match your sound chain:
    // connect nodes to each other through audio engine
    audioEngine.connect(audioPlayerNode, to: pitchEffect, format: nil)
    audioEngine.connect(pitchEffect, to: delayUnit, format: nil)
    audioEngine.connect(delayUnit, to: reverbEffect, format: nil)
    audioEngine.connect(reverbEffect, to: audioEngine.outputNode, format: nil)

Everything else stays the same.
What about that “mixer” thing I mentioned at the beginning? Not related to the “wet/dry mix”, a “mixer” comes into play when you want to combine effects in special ways - from multiple sources, say. Like the outputNode property, the audio engine has a default mainMixerNode to help with this. The WWDC video can give you great info on it if you’re ready to go to that level. But, it works the same way: the mixer just gets added to the sound chain, along with the effects, as appropriate based on how you’re using it. I don't need a mixer, so I'll leave it at that for now.
Feel free to play with this stuff. Changing the order of the chain can sometimes affect the effect. Changing other values within an effect (such as delayTime within the AVAudioDelayUnit object) can create interesting sounds.
Use the free documentation on ChucK => Strongly-timed, On-the-fly Music Programming Language, or check out the book Programming for Musicians and Digital Artists - even if you’re not interested in learning the language of ChucK, you’ll likely find the discussions on the workings of digital music to be helpful in your sound programming efforts.
This has been a very basic level discussion of how to set up some sound effects in iOS using Swift. I know very little about digital music, but I hope I have given you a great start to expanding your own knowledge way past mine! Enjoy!
Footnotes:
ChucK => Strongly-timed, On-the-fly Music Programming Language - free, open source; includes Cocoa and iOS source code that makes up the underpinnings of ChucK. Note: some of the developers of ChucK have a company called Smule - makers of several popular music and sound-related apps on the App Store. See Dr. Ge’s TED talk linked on the ChucK site.
Programming for Musicians and Digital Artists - text for learning to program in ChucK, including discussions on the workings of digital music. (Even if you don’t care to learn ChucK, the digital music explanations can be very helpful!)
DAC:
Digital to Analog Converter - it represents the speakers, headset, or whatever outputs the sound on your computer.
Gain:
I'm probably not going to explain this right, but I'll try based on my understanding: gain is to audio output as bandwidth is to an Internet connection. On the Internet connection, if multiple people are downloading files, one might take up the whole bandwidth while the others have to wait their turn. Alternatively, the network might be set so all of them can download at the same time, but each can only use a fraction of the bandwidth.

Gain, then, is the amount of audio pipeline the sound is allowed to take up. In my ChucK example, I'm splitting my sound output up between 3 panning objects, so each has 1/3 of the sound “bandwidth”. (Note that volume is separate from gain: you could have something at full volume, yet it'll still sound quieter if it only has a fraction of the gain. This is similar to how you can still do a complete download with only part of your Internet bandwidth - the download will just be slower.)
Pan:
Panning only works in stereo. It controls where you hear the sound: left speaker, right speaker, or both, as well as combinations inbetween. As an example, I really like songs or videos that have a car racing by - you hear the car come in from the left, say, then it seems to pass in front of you, then it leaves to the right. This is an awesome use of panning! Listen to this Car Passing By Sound Effect video.