Sh*t my brain says and forgets about

Tag: ios

iOS Background Refresh & Force Killing

If you’ve done any work with background refresh (application:didReceiveRemoteNotification:fetchCompletionHandler:) on iOS 7 you know that it can be a pain to debug your code with.  Some things I’ve discovered:

  1. Background refresh will start your app if it’s not running.
  2. If you force-quit an application, it’s not eligible to be started by a push notification.  Restarting the app or rebooting is the only way to again receive background push wakes.
  3. You can’t really debug wake by push other than with logging.
  4. Touching UI code in application:willFinishLaunchingWithOptions: is risky with background push since it fires but didFinishLaunchingWithOptions won’t if it’s immediately backgrounded.  If you’re using UIStateRestoration, make sure you’re limiting UI code in willFinishLaunchingWithOptions to setting up the root view controller.
  5. Don’t arbitrarily reset the badge count when your app is presented – you should really reset the badge count when the notification is viewed/considered no longer relevant.
  6. Update your push service to send “all clear” or zero count badges when things are read via other instances of your app (web, other iOS device, Android too).  Your users will thank you that they’re cleared everywhere.

Take a look at Apple’s documentation on app lifecycle – there is an excellent set of graphics to demonstrate where things hook in.

NSNotificationCenter Block-based Observer

Back in iOS 4, a nifty block-based observer method was added to NSNotificationCenter:

- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *))block;

Super convenient, right? I love using blocks to pass simple callbacks to controllers instead of creating a delegate protocol. There is a catch with this method, and it’s not terribly obvious unless you’re looking closely. The method returns (id) – according to Apple’s documentation the return object is “An opaque object to act as the observer”.  What does this mean?

Typically when you register your class instance as an observer, you pair it with a removeObserver somewhere else usually in dealloc.  The thing is, removing self will NOT remove block-based observers! The opaque object that the method returned needs to be retained somewhere so that removeObserver can be called with that object. This is especially important if you’re calling self within the block to eliminate a retain cycle/memory leak.

Read more about the method and it’s usage in Apple’s documentation.

Fixing Layer Transparency Issues in Xcode

If you’re looking to get higher frame rates and general application performance tweaks from your iOS application, you may need to take a look at transparent settings on your subviews. Any time you set a subview to be transparent, the OS has to blend multiple layers together to figure out the end flattened result. This blending takes CPU cycles and can impact performance of your app – especially in something as simple as a UITableViewCell.

Luckily there is an easy way to find these problems using Xcode’s companion tool, Instruments.

One of the instrumenting tools you can use on an app deployed to a device is Core Animation. The Core Animation instrument has a bunch of fancy switches you can flip.

coreanimation

That will turn on a visual indicator to show where multiple transparent layers exist on the screen. For example, the main table view of my app MigraineDiary shows the following:

screenshotwithouttransparenciesscreenshotwithtransparencies

To get your app into Instruments, simply select the “Profile” menu item under “Product”. This will build your app and launch Instruments. Make sure you have your device selected and not the iOS Simulator. Any areas that are heavily colored in red should be reviewed by you to ensure you’re not setting layers transparent that don’t need to be. This includes using [UIColor clearColor]!

You’ll notice transparency indicators in red on system components like the tab bar and nav bar. There isn’t much you can do about these, so you can really just ignore them. If you’ve overridden behaviors, however, you may wish to review your code to make sure you’re not breaking something.

iOS Basics: nil vs NULL vs NSNull

Yes, there are three ways to represent a null value in Objective-C.

NULL = Absence of a value with C-style pointers
nil = Absence of value with Objective-C object variables
NSNull = A nil boxed as an object for storage in a collection

If you try adding nil to a NSDictionary or NSArray, you will find out it doesn’t perform as expected.  If you absolutely need to store a null value in a collection, you can use NSNull to represent the lack of a value.  For example:

NSNull nullValue = [NSNull null];
[anArray addObject:nullValue];

Note, however, that since NSNull represents the lack of a value, there is no way to retrieve that value from the collection. In order to determine if there is no value for a particular key or index, you must compare against NSNull.

id value = [anArray objectAtIndex:3];
if (value == [NSNull null]) {
  // Do something for a null
}

iOS – Customize Table View Cells

Ever wanted to have alternated colors on your table view cells? If so, you’ve probably done something inside of cellForRowAtIndexPath and applied a background color to your cell there.

Would you be surprised to know that’s completely wrong?  Yup.  Wrong.  WRONG WRONG WRONG.

I didn’t know this, but any styles applied to cells based on state or whatever should really be in willDisplayCell – NOT when you configure the cell itself!  Per Apple’s documentation for the Table View delegate –

“A table view sends this message to its delegate just before it uses cell to draw a row, thereby permitting the delegate to customize the cell object before it is displayed. This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color. After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.”

Keep this in mind especially if you’re using a NSFetchedResultsController with CoreData.  You’ll have to issue a [tableView reloadRowsAtIndexPaths:…] in your didChangeObject method.  For some reason, at least in iOS 4.3.3, the willDisplayCell isn’t firing with the default code Apple recommends when using a Fetched Results Controller.  Adding that reload does the trick.

iOS – Pull App Version From Bundle Configuration

We all like to put the current application version number in an “about” screen somewhere for users to reference.  It’s a pain, however, to have to update that screen every time we do a release as well as the version in the target configuration for the bundle.  So why not pull the version number from the bundle?  Here’s how to do it:
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey];

NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];

Just put this version number in your label programatically and you’re set!  Since my About screen is in a nib/xib, I set the label text to “Version x.x” so I would understand the label isn’t static next time I look at the screen in Interface Builder.

— UPDATED 3/9/2012

I updated this post with a recent change to the way version numbers work.  Not sure if this was an advertised change by Apple, but this should fix any issues you have.

iTunes Connect – Invalid Binary

I spent the past week pulling out my hair trying to submit an update for Centare’s EyeOnWeather application to iTunes Connect.  I kept getting a reject from the system and all I got for an error message was “Invalid Binary.”  THANKS, THAT’S SOOPER.

Eventually I ended up attempting to contact iTunes Connect Support for further details.  I thought it might have been missing icons, malformed Info.plist, something.  I haven’t changed anything in the project drastically with how it builds, so I was at a loss.  Turns out, I was picking the wrong provisioning profile in my setup.  Man I felt stupid.  Ends up that I’m not crazy – Apple’s documentation on how to set up your project for building still only references Xcode 3.  Awesome for the rest of the world using Xcode 4.  Here are some tips I got from Apple iTunes Connect support for pulling in information to submit to their developer team:

(replace 'path/to/myapp.app" with the actual path to your application):

codesign --verify -vvvv -R='anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] exists and certificate leaf[field.1.2.840.113635.100.6.1.4] exists' /path/to/myapp.app

codesign -dvv /path/to/myapp.app

- This command is useful to see what certificate was used to sign your app. If under the section labeled "Authority" it says iPhone Developer, this means that your app was signed with your Developer Certificate. If it says iPhone Distribution, this means that it was signed with your Distribution Certificate.

codesign -d --entitlements - /path/to/myapp.app

I ended up pulling the EyeOnWeather.app file out of my archive bundle to be sure I was testing what I sent up to iTunes Connect.  You can also just do a “Build for Archive” and then right click your .app in Xcode under Product and “Show In Finder”.  Drag that file into Terminal and it’ll paste the full path & filename.

Running those commands gave me confirmation of this – I was code signing with my developer profile.  Even though you pick the Distribution certificate in the submission window for uploading, it doesn’t change the signature of the app.  The solution was to go into my setup for the build and change the Code Signing Identity for the “Release” configuration to the Distribution profile.  I deleted the old “Distribution” build configuration that I had created back in the Xcode 3 days as it’s no longer appropriate.

This link is the defacto WTF help me document for code signing in Xcode 4:
Technical Note TN2250 – Understanding and Resolving Code Signing Issues

UITextView having rounded corners

I’ve been using a UITextView in an app and realized that it didn’t have any rounded edges like the default behavior exhibited by the built-in iOS apps.  For example in the calendar app, setting the Notes field shows:

Inside of Apple’s Human Interface guidelines it specifically states “A text view is a rounded rectangle of any height. A text view supports scrolling when the content is too large to fit inside its bounds.”  Adding a UITextView to a view shows square corners by default.  So how the hell do you get rounded corners?  Interface Builder doesn’t show anything about corners.  Seems like the only way to get this behavior is by doing the following in code:

  1. Add: #import <QuartzCore/QuartzCore.h>
  2. And then add: textView.layer.cornerRadius = 10.0f;

Replace textView with the variable pointing to your UITextView.

Xcode 4 – Problem submitting App with Static Library

I’m submitting a new version of my Migraine Diary App to the App Store and was running into problems with Xcode 4 giving me the following error: “[Your App Name] does not contain a single-bundle application or contains multiple products. Please select another archive, or adjust your scheme to create a single-bundle application.”

There is an issue or maybe it’s an intentional design thing with Xcode 4 and how it handles statically built libraries being included in your project.  I’m specifically using Core Plot and it’s instruction set hasn’t been updated for Xcode 4 yet.  Here are the things I had to do to get Core Plot to bundle correctly with my App to submit it:

  1. Click on the Core Plot project which should be a child of your App’s project.
  2. Click on the Project CorePlot-CocoaTouch and go to the Build Settings.  Set “Skip Install” to Yes.
  3. Click on the CorePlot-CocoaTouch target and set “Skip Install” to Yes.
  4. Click Build Phases and under Copy Headers, move all of the Public and Private entries to the Project section.

You should then be able to build your project for Archive and submit to Apple.

Thanks to Apple Dev Forums – https://devforums.apple.com/thread/86137

iOS Basics – UINavigation Controller & Back Button Text

I’ve brought an old project out of the moth balls recently, the Migraine Diary application I wrote as part of my master’s thesis.  It was my first “real” iPhone app and I call tell I didn’t know what I was doing entirely looking through the code.  What this has forced me to do, however, is re-learn some of the basics of iOS development and of Apple design patterns.  I have been spending some time back in the Apple developer documentation and will probably be posting some of the gotchas that tripped me up two years ago and I’m solving now with the better, more elegant solution.

Navigation controllers are very useful things to have in your app, and are probably the easiest thing to implement WRONG.  Two years ago, I assumed any time I needed a navigation bar up top that I needed to create a UINavigationController and make my view the root controller of it.  Silly me.

One of the problems I was facing in the Migraine Diary app was changing the back button text when I was one or more levels deep into a navigation controller.  My main view was titled “Journal Entries” and my child view “Journal Entry”.  By default, the controller will title the back button as “Journal Entries” which you can see clutters up the screen.

I found a lot of ways of hacking the title online, but there is really only one “right way” according to Apple.  The button titled “Journal Entries” is actually owned by the parent view.  Before you push the child view onto the navigation controller, do something like this:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”Back” style:UIBarButtonItemStyleBordered target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backButton];
[backButton release];

Take note that you’re referring to [self navigationItem] not [[self navigationController] navigationItem] since you are in the PARENT controller.

That’s it!

Page 2 of 2

Powered by WordPress & Theme by Anders Norén