Posts tagged ‘iPhone SDK’

Xcode test coverage falsely indicates methods covered by testing

If you have a normal Xcode project that you added unit testing to, you may find that the testing code coverage inside of Xcode shows that there is a lot more code being tested than actually is. Or if you have an app that does complex and/or lengthy things in its initialization, you may get tired of waiting for all that stuff to run just because you want to run the tests to see if they pass.

Luckily, I figured out a way to short circuit the app from its normal initialization if you are invoking it for the purposes of testing. Here is the code you would need to put in your app delegate’s didFinishLaunchingWithOptions method, preferably right at the top:

#if defined(DEBUG_VERSION)
    if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-FNTesting"])
    {
        DebugLog(@"The -FNTesting argument is in use, so the app will just create a blank view controller");
        UIViewController *vc = [[UIViewController alloc] init];
        navigationController = [[MyNavigationController alloc] initWithRootViewController:vc];
        [window setRootViewController:navigationController];
        return YES;
    }
#endif

(The #if above uses a compiler variable that is only used in the debug scheme of the application. It’s a good plan all around to do this in your own code.)

Basically, this code only is going to run if your are doing a unit test run, and it just creates a blank view controller and returns.

BTW, Happy Pi Day. I meant to do this posting at 1:59 today to continue the pi theme, but got busy with work and forgot.

Problems presenting view controller from clickedButtonWithIndex on iPad with iOS 8

As I work through my apps, I keep finding nifty iOS 8 issues. Such as one where I am having problems presenting a view controller from the clickedButtonWithIndex delegate method on an iPad with iOS 8.

From my research, it appears that under the covers, Apple is taking my UIActionSheet and morphing it into a UIActionController on the iPad in iOS 8. Unfortunately, weird things can happen if you try to present another view controller from within the clickedButtonWithIndex method, as the alert controller is still visible when the new controller is being presented. As a result, you get a warning message in the console that looks something like this:

Warning: Attempt to present <NewViewController: 0x12345678> on <OldViewController: 0x98765432> which is already presenting <UIAlertController: 0x24682468>

The solution to this seems to be to react to the didDismissWithButtonIndex method on UIAlertViewDelegate. When this is done, the alert controller appears to be gone on the iOS 8 iPad by the time that the new view controller is presented, and all is happy.

BTW, a posthumous Happy Birthday to Paul Butterfield, a fantastic artist who left us way too early. Luckily he is being immortalized in the Rock and Roll Hall of Fame, so more people should learn about him and experience his music.

CLGeocoder with built-in NSCache

In case you were wondering, Apple “recommends” that you only make one call per minute to their geocoding system. I can kind of understand the reasoning behind this, as they do not want to let people abuse the system. If you have a bunch of geocoding requests that you need to make within a short amount of time, you will not be cut off immediately if you push through a bunch of requests within seconds, but eventually you will get clipped. Now, if the requests you make are part of a table view that is scrolling, wouldn’t it be neat if you could find a CLGeocoder with built-in NSCache?

Well now you can. Please check out my GitHub repository for BPGeocoder:

https://github.com/Wave39/BPGeocoder

This class inherits from CLGeocoder and you use it as a replacement for CLGeocoder, except that it maintains its own NSCache of addresses that it has geocoded, and if you pass in an address it has already seen, it does not bother contacting the Apple servers, it just returns back the cached results it found earlier.

BTW, Happy Birthday to Terry Farrell, who played Valerie in Back To School and Lt. Dax on Star Trek: Deep Space 9, which was the best Star Trek series, IMHO. (Additionally, I think we need to get an internet campaign going to get IMDb to change the cast photo on their DS9 page to be one with Terry. Leave a comment if you agree. Sorry, Nicole!)

EXC_BAD_ACCESS when creating Address Book records on iOS

I have this code in one of my apps that creates some test records in the Contacts app, so that I can quickly build up some address book data for testing purposes. However, I just uncommented out this code for the first time in a long time, and now I am getting some kind of object release EXC_BAD_ACCESS exception when creating Address Book records on newer versions of iOS.

I tracked the problem down to this line:

ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABPersonPhoneProperty);

While this used to work just fine, I needed to change it to this line:

ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);

And now my address book records are created without any kind of memory crash.

BTW, Happy Anniversary to Godzilla, who premiered in Japanese theaters on this day 60 years ago. Additional BTW, for U.S. readers, don’t forget to get out and vote tomorrow.

resignFirstResponder does not put away keyboard on UIModalPresentationFormSheet

I started to use a few view controllers inside of navigation controllers that are presented with the UIModalPresentationFormSheet style, and to my dismay discovered that the resignFirstResponder call does not make the keyboard disappear.

Apparently there is a reason for this, if you are interested you can look it up and get a full explanation. For my purposes, I was just happy to discover that overriding the disablesAutomaticKeyboardDismissal method and returning NO seems to do the trick. Basically, now when I need to present a navigation controller in this fashion, I alloc the navigation controller with the class of MyNavigationController instead of UIViewController. Here is the code for MyNavigationController.m:

#import "MyNavigationController.h"
 
@implementation MyNavigationController
 
- (BOOL)shouldAutorotate
{
    return YES;
}
 
- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskAll;
}
 
- (BOOL)disablesAutomaticKeyboardDismissal
{
    return NO;
}
 
@end

You will of course need to change the rotation methods to your own needs.

Today, I have a double BTW for you. Happy birthday to Joe Bonamassa, one of my favorite guitar players and all around performers. And on a sad note, one year ago today, Jeanne Cooper, the grand old dame of The Young and The Restless, passed away. The show has not been the same without her.

Unable to simultaneously satisfy constraints

All of a sudden, I started getting this error popping up in the console while developing my iOS 7 app:

2014-04-30 11:32:27.199 MyApp[4495:60b] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; 
	(2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't
	understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x1946a510 h=-&- v=-&- DetailTableView:0xe39ce00.width == UIView:0x2613afd0.width - 293>",
    "<NSAutoresizingMaskLayoutConstraint:0x12ccfd50 h=--& v=-&- H:[UITransitionView:0x12ca7570(768)]>",
    "<NSAutoresizingMaskLayoutConstraint:0xcf18c40 h=-&- v=-&- UIView:0x2613afd0.width == ADTransitionView:0xcf16f20.width - 768>",
    "<NSAutoresizingMaskLayoutConstraint:0xcf19bb0 h=-&- v=-&- ADTransitionView:0xcf16f20.width == UITransitionView:0x12ca7570.width>"
)
 
Will attempt to recover by breaking constraint 
<NSAutoresizingMaskLayoutConstraint:0x1946a510 h=-&- v=-&- DetailTableView:0xe39ce00.width == UIView:0x2613afd0.width - 293>
 
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

A few notes about the app:

1. I am still a noob when it comes to Auto Layout, so I have been kind of easing into it on some of the less critical areas of the app, but the part of the app in question is still all springs and struts.
2. I am using the ADTransitionView library to do customized view controller transitions.
3. It did not do this until this morning.

If I hit continue a couple of times on the debug console, the app moves along as if there is no problem, so this is not a critical issue, but I still wanted to find out how to take care of this issue first, and then try to figure out what triggered it.

The way that I was able to get around the issue was to look at all of the nib files that make up my view controller, table view, and cells. I found that one of the component nib files was still flagged to use Auto Layout, even though there were no constraints of any kind in that nib file. When I turned off the Auto Layout flag on that file and ran the app, it started to transition between view controllers without the ‘Unable to simultaneously satisfy constraints’ message.

BTW, Happy Birthday to Phil “Scrap Iron” Garner, who was an infielder for the Pittsburgh Pirates in the late 70’s and early 80’s.

NSString to UIImage

I had the need to take a regular old NSString and get a UIImage representation of that text. It seems like a relatively straightforward thing to do, so here is the code that I put together:

+ (UIImage *)imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}
 
+ (UIImage *)imageWithString:(NSString *)theString font:(UIFont *)theFont color:(UIColor *)theColor
{
    UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    tempLabel.backgroundColor = [UIColor clearColor];
    tempLabel.textColor = theColor;
    tempLabel.font = theFont;
    tempLabel.text = theString;
    [tempLabel sizeToFit];
 
    return [self imageWithView:tempLabel];
}

As an added bonus, if you happen to be using Font Awesome in your iOS project (and let’s be honest, if you aren’t, you should be), here is a bonus method:

+ (UIImage *)imageWithFontAwesomeIcon:(NSString *)faIcon fontSize:(CGFloat)fontSize color:(UIColor *)fontColor
{
    return [self imageWithString:[NSString fontAwesomeIconStringForIconIdentifier:faIcon]
                            font:[UIFont fontWithName:kFontAwesomeFamilyName size:fontSize]
                           color:fontColor];
}

BTW, Happy Birthday to Brian Setzer, who is a fantastic guitar player and singer.

UITextView font in IB not respected

One thing that I found to be a bit odd is that, with the latest Xcode 5 and iOS 7 SDK, if you create a UITextView inside of a UITableViewCell in Interface Builder, and change the font of the text view, it will not use your changed font in the simulator or on a device. After checking with Stack Overflow, I found that if I ticked the Selectable property for my UITextView, all of a sudden it started to respect the new font that I had selected. I am not sure if that is a bug or by design, anyone have any ideas or cite documentation one way or the other?

BTW, Happy Birthday to Leonard Nimoy. Live long and prosper.

Use NIB-based UITableViewCell properly

I like to create my complex UITableViewCell objects in their own NIB files, as it helps me keep things well organized. Unfortunately, I felt like I was not able to use NIB-based UITableViewCell properly, as the implementation of this was always a bit clunky in my opinion, since I was relegated to doing something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *CellIdentifier = @"CellIdentifier";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
    {
        [[NSBundle mainBundle] loadNibNamed:@"MyCustomCell" owner:self options:NULL];
        cell = myCustomCell;
        self.myCustomCell = nil;
    }
 
    // do something here with the cell
 
    return cell;
}

Ugh, this was a bit messy. You had to make sure that your MyCustomCell.xib file had the file’s owner pointing back to your view controller class, and that you set up an IBOutlet to the cell.

The ability to take a project and require the latest version of iOS can be a refreshing thing, as it allows one to come up to the latest APIs and practices without having to maintain painful backward compatibility. Just such a thing happened today as I learned about the proper way to use a UITableViewCell from a NIB file.

This process involves registering a class or NIB file to work with a UITableView, hooked in through the cell identifier. To set up this registration, you would do something like this in your viewDidLoad method:

UINib *nib = [UINib nibWithNibName:@"MyCustomCell" bundle:nil];
[_theTableView registerNib:nib forCellReuseIdentifier:CellIdentifier];

(Just make sure to set up your CellIdentifier NSString variable as a global in the class or as a static in the same area that you register the NIB.) And then, the cellForRowAtIndexPath method has a little less heft to it:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    // do something here with the cell
 
    return cell;
}

Because you have registered the NIB file with the cell identifier on your table view, the dequeueReusableCellWithIdentifier method knows how to either reuse or create a cell without having to explicitly do it in code. I like less heft.

BTW, Happy St. Patrick’s Day to everyone, Irish and non-Irish alike. Please be safe out there and don’t do anything dumb, which is actually a good rule to live by the other 364.24 days of the year as well.

Core Data error: The operation couldn’t be completed. (Cocoa error 19.)

Well, it has been over a year since I last had a customer with a corrupted database problem. Which means that I guess I was overdue. And what problem it is.

My customer is reporting that they now get the following error on their iPad when they try to do something with their database:

cocoa_error_19

Just in case you can’t read the text in the photo, it says:

The operation couldn’t be completed. (Cocoa error 19).

After some trouble getting the file (it is over 4 GB, not easy to e-mail), I saw a bunch of errors pop up in my debug console, including the dreaded “The database disk image is malformed”, which to me indicated that I needed to try and repair the database file. So I punched up my blog post from February of last year to try and run a repair on it.

Unfortunately, I ran into one dead end after another. I was able to dump the data out of the original database file, which produced a text file of over 8 GB. However, no matter what I tried to do to import this data back into a new database, I would end up with an empty new database.

On a lark, I decided to switch to Windows to try and fix the database file. After downloading the tools from the SQLite web site, Here is what I ended up with on my Windows computer:

C:\database>dir
 Volume in drive C is Windows7_OS
 Volume Serial Number is 50B0-39AA
 
 Directory of C:\database
 
03/14/2014  06:27 PM    <DIR>          .
03/14/2014  06:27 PM    <DIR>          ..
03/14/2014  06:16 PM     4,251,181,056 DB.sqlite
03/11/2014  11:33 AM           536,064 sqlite3.exe
03/11/2014  11:34 AM         1,388,032 sqlite3_analyzer.exe
               3 File(s)  4,253,105,152 bytes
               2 Dir(s)  240,466,235,392 bytes free

So I fired up a command prompt and tried to run some SQLite commands:

C:\database>sqlite3
SQLite version 3.8.4.1 2014-03-11 15:27:36
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open DB.sqlite
sqlite> .tables
.
.
Some table names listed here
.
.
sqlite> pragma integrity_check;
*** in database main ***
On tree page 1033353 cell 0: Failed to read ptrmap key=-280958431
On tree page 1033353 cell 0: 152 of 407 pages missing from overflow list starting at 1037333
Page 1037588 is never used
Page 1037589 is never used
.
.
You get the idea...
.
.
Page 1037684 is never used
Page 1037685 is never used

After going through the series of steps that I outlined in my message from last year, it appeared to process the 8 GB text file into a new database that was almost the same size as the original database file. Here was what my directory then looked like:

C:\database>dir
 Volume in drive C is Windows7_OS
 Volume Serial Number is 50B0-39AA
 
 Directory of C:\database
 
03/14/2014  06:45 PM    <DIR>          .
03/14/2014  06:45 PM    <DIR>          ..
03/14/2014  06:41 PM     8,428,761,778 dump_all.sql
03/14/2014  06:16 PM     4,251,181,056 DB.sqlite
03/14/2014  06:45 PM     4,239,932,416 DB2.sqlite
03/11/2014  11:33 AM           536,064 sqlite3.exe
03/11/2014  11:34 AM         1,388,032 sqlite3_analyzer.exe
               5 File(s) 16,921,799,346 bytes
               2 Dir(s)  227,795,791,872 bytes free

This had me feeling pretty good. Just to check, here is what I did next:

C:\database>sqlite3 DB2.sqlite
SQLite version 3.8.4.1 2014-03-11 15:27:36
Enter ".help" for usage hints.
sqlite> .tables
.
.
Some table names listed here
.
.
sqlite> select count(*) from table_name;
8716
sqlite> pragma integrity_check;
ok
sqlite> vacuum;
sqlite> .exit

It looks like success. I will have to take this data file and put it back on one of my test iPads when I get back into the office and try it out.

BTW, Happy Pi Day to everyone out there, whether you are a math person or not.

ADDENDUM: After putting the rebuilt SQLite database file back onto my own test iPad and simulator, it worked fine with no issues or cryptic error messages.