Showing posts with label iphone. Show all posts
Showing posts with label iphone. Show all posts

Tuesday, April 11, 2017

UINavigationController - ask for confirmation on Back

Didn't expect this to be such a problem, but once you tap into the UINavigationController things become hairy.

Requirement:

When user leaves a screen by tapping on a "back" in navigation bar and there are changed data in the screen I should ask for a confirmation and keep user at the current UIViewController if she decided to continue editing the data.

Solution.

You may run into this: http://stackoverflow.com/questions/1214965/setting-action-for-back-button-in-navigation-controller/19132881#19132881 (particularly this: https://github.com/onegray/UIViewController-BackButtonHandler).

Once, I was trying to solve the keyboard accessory to be shown each time for each UITextField on shouldBeginEditing by writing a category for a UITextField. And here is something I learned in a hard way:

When you plan or see any category re-writing the existing framework method, STOP! Simple as this and go read on what can turn wrong with this.

The solution mentioned above use this:

1
2
3
4
5
@implementation UINavigationController (ShouldPopOnBackButton)

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

 if([self.viewControllers count] < [navigationBar.items count]) {

No go for me, no need to study, excuse for being abrupt :). But one part of code from this solution turned actually to be useful.

One of the comments on another post: http://stackoverflow.com/questions/20327165/popviewcontroller-strange-behaviour got me here: http://blog.macca.tech/2013/11/ios-prevent-back-button-navigating-to.html

And was not I lucky? It really makes sense, no private APIs, framework's UIViewController gets the chance to do its stuff always. What I wanted to improve though was that "safeDelegate" property and the way it is established. So I added a new method (into UISafeNavigationController.m):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-(id<UISafeNavigationDelegate>) popDelegate
{
    UIViewController *topController = [self topViewController];
    
    if ([topController conformsToProtocol:@protocol(UISafeNavigationDelegate)]) {
        return (id<UISafeNavigationDelegate>)topController;
    }
    
    return nil;
}

And then you can just substitute safeDelegate with popDelegate:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    if (self.popDelegate && ![self.popDelegate navigationController:self
                                             shouldPopViewController:[self.viewControllers lastObject]
                                                                 pop:^{ [super popViewControllerAnimated:animated]; }])
    {
        if (self.navigationBar) {
            [self restoreViewsForNavigationBar:self.navigationBar];
        }
        return nil;
    }
    
    return [super popViewControllerAnimated:animated];
}

Also note lines 7-9 where I call a new method (borrowed from the first stackoverflow solution that I actually criticize :)):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-(void) restoreViewsForNavigationBar: (UINavigationBar *) navigationBar
{
    for(UIView *subview in [navigationBar subviews]) {
        if(0. < subview.alpha && subview.alpha < 1.) {
            [UIView animateWithDuration:.25 animations:^{
                subview.alpha = 1.;
            }];
        }
    }
}

This is to avoid the back arrow in the navigation bar looking as disabled when answer from our controller to the shouldPop is NO.

Then protocol method in the related view controller may look like:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (BOOL)navigationController:(UINavigationController *)navigationController
     shouldPopViewController:(UIViewController *)controller pop:(void(^)())pop
{
    if (!_item.id) {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:LSSTRING(@"Save the item?") message:LSSTRING(@"You are closing this screen by using Back button and have not saved the item.") preferredStyle:UIAlertControllerStyleAlert];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Save and close") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            [self done:nil];
        }]];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Don't save and close") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
            [self doCancellationCleanup];
            if (pop) {
                pop();
            }
            
        }]];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Cancel") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
            
            
        }]];
        
        [alert show];
        return false;
    }
    
    return true;
}

Hope this can help someone! All the credit goes to Hong Kong Web Entrepreneur guy! Once again, here: http://blog.macca.tech/2013/11/ios-prevent-back-button-navigating-to.html

[UPDATE] In the end I had to rework the final part presented here - showing the confirmation as the way it is presented was not releasing the controller correctly and was not cleaning up the views as well. Quite bigger effort is required to get it right and it is not that generic in the end. But this is a good start! :).

Thursday, March 16, 2017

Inherited uiviewcontrollers references in one storyboard. This class is not key value coding-compliant for the key ...

I often inherit my UIViewControllers to add functionality on top of already existing. All works fine, but today I ran into the weird problem. Setup like this:



Where rangeItemEditor view controller inherits from itemEditor view controller. All outlets connected in the parent view controller. On row tap I do performSegue and then this:

This class is not key value coding-compliant for the key ...

Complaining about one of the outlets. After standard troubleshooting, checking that .destinationViewController is right, bunging my head a bit against the wall and trying [navigationController push] which worked I looked more carefully at the Storyboard ID values for these references. Here is obviously a good one:



And the other one, inherited rangeItemEditor had it empty. Filling it in and running again proved the idea that missing Storyboard ID in Identity tab for a references storyboard is a bad thing. I was not clearing this out though, not sure how it happened to be empty.

Thought I'd share to make your troubleshooting of similar cases faster!

In the end I got rid of these references in the storyboard and used [navigationController push]. Why? I just really don't want these crashes when xcode UI Editor decides to remove something behind the scenes. I've witnessed this "open the file -> get the mess" already in xcode storyboards and I really better stay safer here with using old gold manual push. Staying away from the magic until it has predictable results :).

Monday, February 20, 2017

Working around WITH not being available in older versions of sqlite.

For one of my iPhone apps I need to rename route points to match their order as they are inserted or deleted, I started with an easy WITH version:


[NSString stringWithFormat:@"WITH wcte (id, wname) AS (SELECT w.id, '%@ ' ||  (SELECT COUNT(*) + 1 FROM waypoint WHERE vY1 < w.vY1) as wname FROM waypoint w)  UPDATE waypoint SET \"name\" = (SELECT wname FROM wcte WHERE id = waypoint.id) WHERE \"name\" LIKE '%@ %%' OR \"name\" is null OR \"name\" = ''", LSSTRING(@"Point"), LSSTRING(@"Point")]

And ran into the problem when testing on iOS8.1, obviously its version of sqlite didn't support WITH at that time.

So here is the workaround solution for older sqlite versions:

[NSString stringWithFormat:@"UPDATE waypoint SET \"name\" = (SELECT '%@ ' || (SELECT COUNT(*) + 1 FROM waypoint w WHERE w.vY1 < w1.vY1) FROM waypoint w1 WHERE w1.id = waypoint.id) WHERE \"name\" LIKE '%@ %%' OR \"name\" is null OR \"name\" = ''", LSSTRING(@"Point"), LSSTRING(@"Point")]

Not that nicely looking, but I'm still committed to support iOS8 for a few more months. If you are puzzled by that LSSTRING part - that's just my macro for the LocalizableString as I only want to rename these points that are named automatically and surely I want to name them in the localized manner. Punto it is in Spanish (I hope) :). vY1 is a cryptic name for the order column :).

Would not be publishing at all, but sqlite syntax sometime is surprising in what it can or can't do, so I thought I might save time to someone.

If you are into hiking, fishing, cycling or classic skiing here is the app link, it's free: https://itunes.apple.com/us/app/id1120906807


Thursday, May 26, 2016

Symbolicating bitcode crash logs in XCode

As of Xcode version 7.3 there is still a problem with symbolicating the bitcode crash logs. I've attempted several solutions, here and here, but here is what I came to:

1. Crash logs provided are for the dSYMs that Apple also provides. Just go to the iTunes connect and download them:



Once you have them right click on the crash in xcode -> "Show in finder" and copy to some target directory - that's actually a crashpoint file you are going to get.

My target directory is now ~\Desktop\crashes and here is how it looks:



Where #1 are dSYMs as copied from the iTunes connect. #2 is the crashpoint saved from the crash organizer window in xCode. #4 is a crash log extracted from the crashpoint file (right click and "show package contents")

To get #3 - a symbolicatecrash app, execute this in the target directory (xcode 7.3):


cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash symbolicatecrash

For different versions of xcode the location of symbolicatecrash will be different.

To get symbolicatecrash ready to operate execute:

export DEVELOPER_DIR='/Applications/Xcode.app/Contents/Developer'

Only one step is left! That's to get your crash log symbolicated:

./symbolicatecrash a.crash dSYMs/ > a.log

And the output file a.log is looking like:



Way better, is not it?

There are nuances that I would not touch here, like these dSYM files you downloaded are for different architectures and it would be the best to match your specific crash log to a corresponding dSYM. I'll leave this detail to you, my experience is that function names do match quite well, then it is just about the line of code information you'll be getting. In above picture, given crash and dSYM are matched well, you can see the line of code where the crash exactly happened. Otherwise you'd see +xyz there and that's not that helpful.

This is it. Please don't judge me strictly, I'm not an expert in any field, as I learnt over time :)! Knowing more than I do? Share your knowledge in the comments!

Yours,
Stan.

Thursday, April 14, 2016

xcode - Configure for analyzing. The scheme is not configured for analyzing.

I'm used to run analyzing cycle on my apps when moving closer to their public release. This time xcode (7.3 (7D175)) greeted me with:


"Edit the scheme to enable analyzing, or cancel the action." with that "Edit Scheme ..." was not opening anything and I didn't notice anything extra in the schema editor to enable analyzing.

I even created a clean new project to see if error would be present there as well, and it was.

So after scratching my head a bit I just set a RUN_CLANG_STATIC_ANALYZER as true for the target/build where I needed it:


This way I got the static analysis messages back.

Hope this can help someone!

Update. Or as Jaime Santana commented, just "launch Analysis from the menu option Menu -> Product -> Analyze". Thank you Jamie, that worked!

Friday, November 29, 2013

Apple MKMapView vs. Google iOS SDK map - CPU, compass image, center and rotate.

Why I didn't use native Apple's map a year ago when I was going through the options for my apps?

I was looking for the "live" map that would be able to follow your location, option to rotate so your heading can be always on top of the map and while it rotates it should rotate street names gracefully so they are not shown upside down.

Should I tell Apple maps for iOS6 failed miserably for all of those requirements. So I used Google's alternative. It gave me great rotation, street names handling and nothing to complain about.




Watching iOS7 location videos made me really intrigued, it looked like "oh, Apple fixed it all and brought us the map that can rotate correctly". I was really keen to try and after two months of fixing my apps for iOS7 and releasing them I finally got to try.... And I'm not sure this is it.

Why?

You basically have an option to either center the map yourself or set it up to track by itself:


[self.map setUserTrackingMode:[MKUserTrackingModeFollow|MKUserTrackingModeFollowWithHeading] animated:[true|false]

Well, there are few problems here. 

Rotate and center sync. If you want to rotate with one of the automated tracking options, you'd probably need to sync your rotation with the user location tracking animation. Otherwise, for my naive and short attempts the rotation and auto-tracking create weird effect of user location floating around its center. I didn't follow the path of trying to sync that yet and will see if this is worth an effort.

Compass image. Small extra annoying factor here is that once you rotate the map (set the map camera heading) to something not equal zero, the "compass" thingy appears in the right upper corner:



My first thought was - "oh, I'l move this thingy where I want or disable it for iPad version as I show heading elsewhere". Let me do a read up, let me google for it, let me study Apple's forum for it... What? I can't do it! I love Apple for things that were decided for us, it is part of their spirit and I'm glad they keep it even stronger now than before!

This compass thingy is not a big issue I can move my star button (that happened to be in this place by accident) - I'm not that picky.

It has glitches. From time to time it looks like map zoom is being reset for a moment and than re-established again. I can't really correlate it to whatever I'm doing (as I'm doing nothing but set camera's heading and I do for every location change while zoom "blink" only happens once in a longer while). And the first time I rotate it it goes through the field of black and white rectangular and then does it from time to time, probably when tiles has not been loaded yet for some area I suddenly need to expose by programmatic rotation. This never happens to the Google map.
+ When changing programmatically (or by setting the auto option with WithHeading) heading from 360 to 1, Apple's map rotates -360 + 1 degree. Like, this can't be true? I'm probably just silly. But Google does it right, and it makes me feel less silly and more desperate.
+ Compass image shows even when I have enableRotate to NO, but when phone points exactly to 0 heading, the compass image disappears. So when my user will drive exactly in North direction they know it by not seeing the image?! Very consistent!

I can center and rotate all by myself.

I'm a big boy, if I proved it with Google maps I can prove it everywhere?

Ok. I'm loosing that extra smooth animation for centering the map should I tell. But it just looks as it does for the Google maps, that would suffice. Seems to work just ok. So I was sitting and looking at that center and rotate I put together thinking "Stan, you are a genius, you proved it!".

As I tend to spend few minutes in this state, I felt how my lap is getting hotter, and hotter .... Oh oh, this is my beloved temperature based CPU profiling instrument! 

So here is the reason of why I'll be thinking twice now if I should use native Apple's map for the live map option, let me provide it here. On the top you'll see Apple's map consumption while centering and rotating and below it you'll see the same task executed by a Google map. If simulators doe this to my mac, I guess I can speculate the same CPU/battery consumption ratio will stay on real devices:


My story ends here, enough for this evening, I need to re-group, re-think, re-try, re-study ....








Sunday, September 29, 2013

iOS 7 vs iOS 6 memory consumption. OMG?

Known thing my app starts slower on iPhone 4 with iOS7 then it is on the same iPhone 4 with iOS6. This is pretty sad, but not the only sad thing...

As I'm profiling now the app before its soon to come release to AppStore, I got to the following screenshots for the fresh app start on iPhone 4 with iOS6:


Note the 1.55MB number. And now, the sad thing, same fresh start on iPhone5 with iOS7:


Noticed that 8.21MB?! I currently only have iOS7 installed on iPhone 5, so I can't compare iPhone 4 iOS7 vs iOS6 consumption, but I believe this iPhone 5/4 factor should not play any role here. It is just iOS thingy. 

While not that sad for iPhone 5/5s, it is pretty bad news for my users that are still on iPhone 4, apps are going to consume more. All apps, not only mine... App startup is becoming definitely heavier and those extra allocations in iOS7 are surely standing for more work done by iOS7 while starting the app.

Having read Steve's biography, it looks to me like a step away from the "saving lives" approach to plain MS's "throw more memory on it - problem solved".

May I be wrong? I hope!

Yours,
Stan.

Sunday, October 14, 2012

UX, UX, UX and bad reviews in AppStore

I got that really bad review in UK's AppStore:


did not work at all save your money 
did not work at all sent two text for help but no replies so save your money John H


First, I felt quite lost. "That can't be true!" I was telling myself. I respond to all of my users within 30 minutes when I don't sleep and it might take ~8 hours when it is night by Central European Time. 

There is a certain UX problem I currently have with my app that every 100th customer understands the fact I don't show the speed in my speedometer when there is no GPS lock as app malfunction. There is a GPS indicator showing red and reading OFF when there is no signal, but still, I've started to fix it.

When I reread once again, the part "sent two text for help but no replies" rang my bell quite a bit! I have following options in my app:


So the user tapped on S.O.S, ignored the header "Sent my location", saw the sms window open with his location to share and tried to send it out. I have no idea what phone number he sent that location info to. He obviously tried it twice, getting more frustrated each time, which resulted in that 1* review.

Lesson taken! I'm now changing that S.O.S to "Share my location" and "Feedback" to "Feedback & Support":



A big lesson for me again. I already learnt with my first UX problem that whatever can be misinterpreted by user, will be misinterpreted.

I only have wished it would not take another 10 days to get the updated version into AppStore :). 

Here is free version on my speedo, if you want to judge for yourself :) :



Friday, August 24, 2012

AppStore fake reviews - God protect customers

Can I shout more?! Fake reviews on the AppStore work in a way - get 3 fake 5* launch reviews on US AppStore, 2 5* reviews on others. That will get you to "What's Hot" in that market as minimum. If you are lucky, Apple invites you to "New&Noteworthy".

Then be prepared for getting real users' reviews - "WTF, this app is crap and doesn't work!".

Have no worries, your Indian or Belorussian farm will respond with 5-6 5* star reviews to counter balance!

Nothing you can't fix with fake reviews! 

Ooh, Apple, pleeease! HELP! What is the chance for a fair made app to get to the users!? Pretty much zero! ZEERO!

Android has its problems, though it seems to me at times that fragmentation of devices and OS's may not be that bad for the fair indie dev as fake reviews and corrupted AppStore might be!!! Would it happen be Steve still with us?! Don't believe it! Or don't want to believe it!


AppStore fake reviews - more ...

Raw data for you, my reader. Why it is women's name fake reviews are mostly made from on the Apple AppStore? All those women that can't live without an iphone speedometer app in their car/motorbike?! Within a single day or two? Poor ladies!

Dear Apple, please help! Lets stop those Barbie apps and reviews?! And believe me, this exactly app is Galaxy III far from being accurate or fair! Dear Jessica Lovely, Stella Ried, Smith Erica and Angelina Sea, please just stop!!! HEEELP!


Sunday, July 29, 2012

Testing iPhone location app with Automation


Even if we have now ability to add GPX files to the project and iPhone will even "move" between its points, still what if we want to add speed, altitude etc to our location simulation?

It is all easy and possible! Welcome to Automation tool in Instruments. Let me use my speedometer app as a showcase for it. I'll cram in as much as possible in this single session: find memory leaks while my app is continuously processing (simulated) location changes and gather screenshots for the AppStore (you don't really expect me to do it while I drive?! I may not be here then for the next article :)).

Start up automation from xcode (e.g. do alt-run) with target of your physical iphone:


I'll start with leaks profile:




The app starts on the phone and starts recording automatically. No leaks, even if I've waited for a year. As it does nothing... So lets add some location changes that app have to handle:


Actually, you may stop the recording at any time. We are setting things up right now :). Lets even clean up and delete that idle "run" we produced:


Then, open the library of Instrument tools, and then drag the Automation below the Leaks tool:


Now its time to add an automation script, click on Add and then Create:


Here is my script to simulate a few points:

 var target = UIATarget.localTarget();  
 // speed is in meters/sec  
 var points = [  
                 {location:{latitude:48.8899,longitude:14.2}, options:{speed:8, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:14.9}, options:{speed:11, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:14.6}, options:{speed:12, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:14.7}, options:{speed:13, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:49.2,longitude:14.10}, options:{speed:15, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:49.4,longitude:14.8}, options:{speed:15, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:14.9}, options:{speed:9, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:15.1}, options:{speed:8, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 {location:{latitude:48.8899,longitude:16.1}, options:{speed:3, altitude:200, horizontalAccuracy:10, verticalAccuracy:15}},  
                 ];  
 for (var i = 0; i < points.length; i++)  
 {  
      target.setLocationWithOptions(points[i].location,points[i].options);  
      target.captureScreenWithName(i+"_.png");  
      target.delay(1.0);  
 }  

Note, that in addition to location it includes speed, altitude, and accuracy. Speed and accuracy is what is indispensable to simulate the speedometer behaviour correctly per algorithms implemented.

Keep the other options intact for a moment (script starts with recording) and for leaks investigations we don't need to log the results yet.

Let's start a recording now and see if plain old Stan is going to be ashamed. As script runs enjoy the screen of your app as it reacts on location changes, ooh that power!


Yep, plain old Stan should be ashamed at least a bit. There is a leak! It actually leaks on every location change... I'll let you go through your backtrace etc, Intruments give you enough to find where leak is coming from. In my case I forgot to release the CLLocation retained as a property when I was deallocing the holding object. After the fix, I just redeployed the app to the phone and kicked off recording again. No leaks whatsoever!

So now the bonus part. If you want to automate screenshots gathering for you app just pay attention to this line in the above for loop:


target.captureScreenWithName(i+"_.png");  


For every location/speed point I take a screenshot. To keep them on the disk, setup the logging directory and switch on that logging (below the Add script button). This is enough to get this all safely written to the target directory:



This is it I guess! Make sure you explore everything around as I provided only the essence. See the Editor log, Trace log for automation, it all gives some more insight.

Happy testing and simulating!

More on location testing from me: GPX files in xcode.

Friday, June 8, 2012

Sales of my speedometer have overloaded itunes connect reporting. Seriously!!!

I guess it is my new speedometer app yesterday’s rollout that caused itunes connect sales and trends blackout. What else it could be??!!

image

So for a moment please refrain from buying it on the Appstore until itunes connect sales and trends can catch a breath and get back in shape!!!

A story of an iPhone app rejection – x2

The purpose of this story is to recap my experience with Apple App Review Team to build some knowledge base for myself and hopefully others.

First rejection happened to be with an update to my free app Here&Near – Location notes and GPS tools two months ago. The reason for rejection was app name in info.plist as Here & Near, whilst in app store I was going with Here&Near at the start. I went through “Metadata rejection” changing name of my app in itunes connect, after fixing it took reviewer 7 days before my update was reviewed. And I’m not sure if any role was played by the fact that I pinged the Review Team after 6 days of waiting.

Second rejection happened to me a day ago. It is about my new app Speedometer Speed Alert and this story is more interesting to learn from.
After 5 working days waiting for review, I got into review around 10am Cupertino’s time. All nervous I was waiting for 5 hours until news came from the Review Team. App was rejected for the reason of “having location in UIBackgroundmode but not using persistently location services when in background”. After very prompt exchange of messages via resolution center and creating an appeal I got to explain that my speedometer only use background location tracking when user explicitly switches this mode on.

I provided the team with details on how to switch this mode on and my app went from Rejected to Waiting for Review and to In Review instantly. It was about 5.30pm their time and next Cupertino's morning my app went to Approved! Yupiee!

I'm glad I woke up CET deep night to tackle issues with the review team and I'm totally satisfied with their approach.

What are my lessons:
- When using UIBackgroundMode and not tracking location or playing audio in background by default, provide Apple Review Team ahead of a review with explanation on how to switch the background execution on.

To try next time:
- Next time, I'll try to watch the resolution center messages directly not waiting for email to come. The first message from review team was recorded in resolution center at 11.20am Cupertino's time while I got rejection email 4.30pm their time. I'll try to see next time when this Resolution center links starts to appear and if by chance, there will not be some messages there ahead of the email.

The story continues with my other app rejection story :) http://plainoldstan.blogspot.cz/2012/06/more-on-uibackgroundmodes-location.html

Thursday, May 10, 2012

OMG! I didn't have my appstore keywords right! Now I know it!

I've been tuning keywords for my iPhone app Here&Near (free on AppStore) for quite a while now. Being glad when climbing from 5 downloads per day to 10 (then getting to near zero after some tweaking again :)).

And it looks like during my last update I got closer to understanding what right keywords would be:



I like this download rate much more. Not sure it is going to stay and if I'm going to share this fact :):). Now I'm like "wow, how I could not have seen what I'm missing!?"


My problem, as an app creator, was about seeing keywords through my own lens, not from the user trenches. And I have not taken the exercise of trying to see it through the user eyes... I haven't done it yet! I just rambled to some better keywords because of some round robin rotate almost. Should I have paid more attention to it earlier I could have had much bigger user base!

On the other side, kind of "Apple Keyword Tool" would really help us devs and users, I believe!

Thursday, April 26, 2012

sqlite+iPhone - Check if table exists

 

Progressing now with implementing db schema/data version upgrade for one of my iPhone apps – Here&Near. Location notes and tools. So I thought I’d share a snippet for checking if table exists:

+(bool) tableExistsWithName: (NSString *) name dbPath: (NSString *) dbPath error: (NSError **) error
{
    sqlite3 *db;
    sqlite3_stmt *checkStmt;
    
    int dbrc;
    const char* dbFilePathUTF8 = [dbPath UTF8String]; 
    dbrc = sqlite3_open_v2(dbFilePathUTF8, &db, SQLITE_OPEN_READONLY, nil); 
    
    const char *sql = "select count(*) from sqlite_master where type='table' AND name=?";
    
    if(sqlite3_prepare_v2(db, sql, -1, &checkStmt, NULL) == SQLITE_OK)
    {
        const char *cname = [name UTF8String];
        
        sqlite3_bind_text(checkStmt, 1, cname, -1, SQLITE_TRANSIENT);
        
        int retCode = sqlite3_step(checkStmt);
        
        if(SQLITE_ROW == retCode)
        {
            int count = sqlite3_column_int(checkStmt, 0);
            sqlite3_finalize(checkStmt);
            sqlite3_close(db);
            
            return count == 1;
        }
        else 
        {
            
            [DbHelper convertSqlError:db error:error];
            
            sqlite3_finalize(checkStmt);
            sqlite3_close(db);
            return false;
        }
    }
    else {
        [DbHelper convertSqlError:db error:error];
        
        sqlite3_finalize(checkStmt);
        sqlite3_close(db);
        
        return false;
    }
    
    return false;
}

Where [DbHelper convertSqlError:db error:error] is explained in the previous entry.

I guess I have some semantic mess with NSError in the above code, but providing as it is right now…

Thursday, April 12, 2012

Converting sqllite error to NSError – bridging the Gap

sqllite and Cocoa represent two distinct worlds with different error handling idioms. In order to bridge that semantic gap in my code, I came up with the following helper code:
@implementation DbHelper
 
+ (void)handleSqlError:(sqlite3 *)db error:(NSError **)error
{
    NSLog(@"Error while executing sql statement. '%s'", sqlite3_errmsg(db));
    
    NSError *underlyingError = [[[NSError alloc] initWithDomain:@"db"
                                                           code:sqlite3_errcode(db) userInfo:nil]autorelease];
    // Make and return custom domain error.
    NSArray *objArray = [NSArray arrayWithObjects:[NSString stringWithFormat:@"sqlite error:%s", sqlite3_errmsg(db)], underlyingError, nil];
    NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey,
                         NSUnderlyingErrorKey, nil];
    NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray
                                                      forKeys:keyArray];
    
    *error = [[[NSError alloc] initWithDomain:@""
                                         code:1 userInfo:eDict] autorelease];
}
Usage in the code would be (“select” is “aselect” on purpose):
...
sqlite3_stmt *checkStmt;
const char *sql = "aselect count(*) from sqlite_master where type='table' AND name=?";
...
 
if(sqlite3_prepare_v2(db, sql, -1, &checkStmt, NULL) == SQLITE_OK)
    {
...
}
else 
        {
            
            [DbHelper handleSqlError:db error:error];
            
            sqlite3_finalize(checkStmt);
            sqlite3_close(db);
            return false;
        }
Unit test to see what it would return in case of a simulated error:
- (void) test_when_invalidstatement_should_return_error
{
    NSError *error = nil;
    
    bool exists = [SchemaUpdater tableExistsWithName:@"note" error:&error];
    
    STAssertFalse(exists, @"When statement is invalid, exists should be false!");
    
    STAssertNotNil(error, @"For this test case error should not be nil!");
    
    NSLog(@"%@", error);
}
Where mine SchemaUpdater is just a simple class with snippet shown above having error in sql statement and calling that handleSqlError method.
The output of NSLog is:
Error Domain= Code=1 
"sqlite error:near "aselect": syntax error" 
UserInfo=0xbd28b60 
{NSUnderlyingError=0xbd28b40 "The operation couldn’t be completed. ( error 1.)", 
NSLocalizedDescription=sqlite error:near "aselect": syntax error}
This is it for a moment. You might be also interested in a follow up post: Check if sqllite table exists
...
Or wait! Here are some links that enlightened me on NSError idioms:
http://weblog.bignerdranch.com/?p=360 (definitely take a look if your way of checking for error is error!=nil)
http://www.cimgf.com/2008/04/04/cocoa-tutorial-using-nserror-to-great-effect/ (probably the most comprehensive one)
Stackoverflow nn risk of the NULL dereference
Official:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/Reference/Reference.html
Using and Creating Erorr objects