Posts Tagged ‘iOS’
I’ve recently been working on an app for the iPad and ran into some strange issue, when using a modalViewController. In my case, I wanted the modalViewController to show up right after the app finished launching. I also had a pretty basic viewController setup, similar to the one you get when using the “View-based Application” template.
Inside my rootViewController I wanted to call some other viewController as modalViewController right after start up. It looked pretty much like this:
- (void)viewDidLoad {
[super viewDidLoad];
//instantiate otherViewController and call it as modalViewController
OtherViewController *viewController = [[OtherViewController alloc] initWithNibName:@"OtherWindow" bundle:nil];
self.otherViewController = viewController;
[viewController release];
[self presentModalViewController:otherViewController animated:YES];
}
Unfortunately, when I tested my app nothing happened. The reason why no errors or warnings were shown was simple: The code was fine.
So, what was the problem then? After doing some investigation I found out, that the modalViewController didn’t like being called directly from viewDidLoad. Instead I had to call it with just a tiny bit of a delay.
So, I added a new method to my class:
- (void)callOtherViewController {
if (otherViewController == nil) { //only allocate, if it has not been allocated yet
//instantiate otherViewController and call it as modalViewController
OtherViewController *viewController = [[OtherViewController alloc] initWithNibName:@"OtherWindow" bundle:nil];
self.otherViewController = viewController;
[viewController release];
}
[self presentModalViewController:otherViewController animated:YES];
}
Finally, I only had to call this method from viewDidLoad with the delay mentioned above, like this:
- (void)viewDidLoad {
[self performSelector:@selector(callOtherViewController) withObject:nil afterDelay:0.1f];
}
That did the trick!
UITableViews are awesome. They allow you to display a variety of information, can be customized extensively, take care of memory management and much much more. Since they only remember what’s currently on screen, getting a basic ToDo list up and running can be quite the challenging task for a rookie coder. In this post I’d like to show you how this can be achieved in just a few steps.
First, let’s get all the ingredients ready…
Basically, we need 3 things:
- UITableView to display stuff
- Data source array, that contains the stuff we want to display
- Dictionary, to keep track of what is checked / unchecked
On top of that, we also need:
- UITableViewCell to put inside our UITableView
- UILabel inside our UITableViewCell to display some text
- UIButton inside our UITableViewCell to check / uncheck this cell
@interface TableViewCellDemoViewController : UIViewController {
IBOutlet UITableView *tableView;
NSArray *dataSourceArray;
NSMutableDictionary *checkedDictionary;
}
- (void)checkButtonTapped:(id)sender forCellWithLabel:(NSString *)text;
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) NSArray *dataSourceArray;
@property (nonatomic, retain) NSMutableDictionary *checkedDictionary;
@end
Inside the .m file I need to properly create the cells…
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BaseCell *cell = (BaseCell *)[_tableView dequeueReusableCellWithIdentifier:@"BaseCell"];
if (!cell) {
cell = [[[NSBundle mainBundle] loadNibNamed:@"BaseCell" owner:self options:nil] lastObject];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.cellLabel.backgroundColor = [UIColor clearColor];
cell.cellLabel.font = [UIFont fontWithName:@"Helvetica" size:17];
cell.cellLabel.text = [dataSourceArray objectAtIndex:indexPath.row];
cell.parentViewController = self;
//let's use the cellLabel.text as key inside our checkedDictionary. IMPORTANT: This only works, if information inside cellLabel.text is unique for every row. If you have the same text twice in your datasourceArray, this will cause to some unexpected behavior.
NSString *key = cell.cellLabel.text;
BOOL checked = [[checkedDictionary objectForKey:key]boolValue];
[cell.checkButton setBackgroundImage:(checked) ? [UIImage imageNamed:@"checked.png"] : [UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal];
return (UITableViewCell *)cell;
}
…and add a method, that will get called, if I tap on the check / uncheck button
- (void)checkButtonTapped:(id)sender forCellWithLabel:(NSString *)text {
UIButton *button = (UIButton *)sender;
//like above, we're using the information inside the label (text) as unique identifier = key
NSString *key = text;
BOOL checked = [[checkedDictionary objectForKey:key]boolValue];
//depending on whether the row has been checked before or not, we write YES / NO to our checkedDictionary
if (checked) {
[checkedDictionary setValue:@"NO" forKey:text];
checked = NO;
}
else {
[checkedDictionary setValue:@"YES" forKey:text];
checked = YES;
}
//finally set the new button background image
[button setBackgroundImage:(checked) ? [UIImage imageNamed:@"checked.png"] : [UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal];
}
Finally, I create an empty user interface for Interface Builder and add a UITableViewCell object to it. I also create a new class, named “BaseCell” and set it as custom class for my UITableViewCell.
After adding a UILabel and UIButton object to my cell, I properly link everything and my BaseCell.h file will look like this:
#import <UIKit/UIKit.h>
@interface BaseCell : UITableViewCell {
IBOutlet UILabel *cellLabel;
IBOutlet UIButton *checkButton;
UIViewController *parentViewController;
}
- (IBAction)checkButtonAction:(id)sender;
@property (assign) IBOutlet UILabel *cellLabel;
@property (assign) IBOutlet UIButton *checkButton;
@property (assign) UIViewController *parentViewController;
@end
Last but not least, the .m file will make a call to “checkButtonTapped” method inside my UITableViewController’s class like this:
- (IBAction)checkButtonAction:(id)sender {
if (self.parentViewController) { //call method inside TableViewCellDemoViewController and submit the sender object (= button) and the text inside the label. We will use the text as unique identifier (=key) inside our checkedDictionary (also part of TableViewCellDemoViewController class)
[self.parentViewController performSelector:@selector(checkButtonTapped: forCellWithLabel:) withObject:sender withObject:[self.cellLabel text]];
}
}
If we break down the steps, our app will basically perform the following tasks:
-
Set up custom UITableView cells, containing a UILabel and UIButton
-
If user taps on the button, make a call from BaseCell class to TableViewCellDemoViewController class and submit both the sender button and text inside the UILabel
-
Use the UILabel text to look up a matching key inside our “checkedDictionary” object
-
Change background image of the UIButton, depending on whether (3) exists and whether it is YES or NO.
Once you’ve spent several weeks or months on developing your latest and greatest app and finally submitted it to the AppStore, the real hard work is just about to begin. Lots of developers new to the iOS business think, that it’s enough to create a solid product, but as a matter of fact it’s not. You have to understand, that once your app goes live, it’s competing against half a million apps and counting. The market became real tight and especially small teams and “one man bands” are feeling this now more than ever.
I’ve developed and released more than 40 apps in the past 3 years and therefore had my fair share of success and failure. When doing research on app marketing, I read lots of posts on various forums and talked to other developers. I soon discovered an unpleasant pattern.
Let’s assume, developer A released a product named XY and let’s also assume, that we are not talking about some useless product, like those infamous farting apps. The first day, after his product went live, our developer A checked his daily revenue report on iTunesConnect. He has sold 50 copies (which is actually quite good these days) on the first day. The next day he sold 33, then 17, then 5, then 0. Now let’s do the math: 50 + 33 + 17 + 5 = 105. Since he charged $0.99 per app, he made around 50 bucks. That’s quite bad, because he can’t even take his girlfriend out to dinner, let alone pay any bills he most certainly has.
So, what did our poor developer A make wrong? He certainly didn’t make a bad product. However, since his app got out of possible customers’ sight, it soon drowned in a sea of other great, good, ok, mediocre and bad products.
Visibility!
We need to learn from his mistake, so we’re not doomed to repeat it. It’s all about VISIBILITY. Let me say it again: VISIBILITY. Now you repeat it: VISIBILITY! That’s the key to success these days. Our product has to be visible. If not, the game is over, before it really started. It’s a well known fact, that most of the time, people make the decision whether they want to buy an app or not within milliseconds. They see it – they want it – they buy it. Since we have no control over our dear customers’ wills, the only thing we can do, is SHOW them our product. One last time: That’s VISIBILITY.
We need to draw attention to our product!
The first step towards visibility is to draw attention to our product. If we get mentioned by a blog and readers find our app interesting, they might turn into customers. In case they really like what we’ve created, they will probably tell their friends about it and those people will tell their friends and so on. In our time and age, social networks such as Facebook, Twitter, LinkedIn and similar services have become very important. We can easily connect with other people and share comments, suggestions and interests with just a single click.
It’s also a good idea to implement social features directly into your app. For example, allow people to recommend your app to friends via email or tweet about it.
Be socially active!
No matter, whether you’re developing apps as an individual or a group / company, make sure that you’re socially active. Connect with as many people as possible and maintain a clean and professional online profile. Share interesting news and updates, but avoid mixing them with the latest impressions on heavy partying. The more people you get to follow you online, the more potential customers you will find in the long run. It will also help you to find new interesting projects and make progress both personally and professionally.
Make your product stand out!
In case you do not have the money for hiring some big shot marketing company to do the heavy lifting for you, there are several things you can do, that will only require little or no investment at all:
-
Have a meaningful and typo free product description on iTunes.
-
Create a website for your app: You most likely already have a site, showing general information about your company and your recent portfolio. However, if you believe in your product and that’s what you really should do, a dedicated website is the way to go. In case you’re not too familiar with web development in general, you can buy a good looking web template (google iPhone web template) and modify it to fit your needs without too much hassle.
-
If you blog about your app, post links, tweet about it, etc… make sure to link to your app website (1) and avoid sending users directly to iTunes. You have a lot more control over product placement on your own website, than on the default iTunes page. People find it more interesting to browse a unique and great looking website. They will eventually click the link on this website to open iTunes, if they like your app.
-
Create a meaningful press release (check out PrMac)
-
Create a video of your app and post it on YouTube or similar sites.
-
Try to get your app reviewed: Google “iPhone app review sites” and send requests to sites, that might be interested in taking a look at your product. From experience I can tell, that most of the time you’re probably gonna get ignored. Don’t take that personally. Most big review sites receive way too many requests and so it’s like playing the lottery. Maybe you’re gonna luck out. So, giving it a shot won’t hurt. However, I can not recommend paying for reviews. Smaller sites will sometimes get back to you and offer a review for 20 or 30 dollars. Their traffic is usually low and so you’re most likely burning your money, instead of making a useful investment. In this case stick to the press release (3).
-
Share promo codes. Apple gives you 50 promo codes per app (version of that app) you release. USE THEM! Redeemed promo codes are treated like purchases on the AppStore. They might help you improve the ranking (VISIBILITY) of your app and also spread the word.
Keep your product alive!
Make sure to test your product as thoroughly as possible. Get family, friends or whoever you can find to use your app. Check out TestFlight – it’s an amazing service for tasks like this. If you or someone else discovers a bug, fix it! If your app is missing a feature and you can afford to implement it in a reasonable amount of time, do it! Users love a well maintained app. Product and customer care automatically generate happy users and therefore returning customers. Something, that will eventually improve the reputation and value of your company and future products.
I hope, this little insight will be helpful to fellow developers out there. Feel welcome to add comments / suggestions below, as I’m sure there is a lot more to consider. I’m looking forward to update / improve this article over time.
In case you’ve ever played around with a sectioned UITableView and tried to delete rows from it, you’ve most likely experienced a crash once in a while.
When you read the console log, most of the time you find an entry similar to this one:
*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Invalid update: invalid number of sections. The number of sections contained in the table view after the update (0) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).’
Usually, your code for deleting stuff from your UITableView will look like this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) { //let's delete something from our table
//IMPORTANT - modify data source BEFORE deleting rows from the table
[mySourceArray removeObjectAtIndex:indexPath.row]; //we assume, that mySourceArray is a NSMutableArray we use as our data source
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
Now, things can get a bit more complicated when using a sectioned UITableView. In my case, I’ve been using a NSMutableArray (filled with NSDictionaries) as data source.
It looked like this:
NSMutableArray ->
……….-> NSDictionary:
………………..-> “headerTitle”: “A”
………………..-> “rowValue”: “RowValue1″, “RowValue2″, etc…
………..-> NSDictionary:
…………………-> “headerTitle”: “B”
…………………-> “rowValue”: “RowValue1″, “RowValue2″, etc…
etc,…
As you can see, a datasource like that is just a bit more complex than values directly inside a NSMutableArray.
In order to avoid the crash mentioned above, I had to make sure, that I only deleted rows from my table, when there were still OTHER rows left inside the same section. In case the row I wanted to remove was the last one within a section, I had to remove the entire section. Btw, that’s exactly what the crash log said
Here is my method again, this time with the needed modification:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) { //let's delete something from our tableNSDictionary *dict = [mySourceArray objectAtIndex:indexPath.section];
NSArray *array = [dict objectForKey:@"rowValue"];
int arrayCount = [array count]; //get the current count of the array
if(arrayCount == 1){ //we are removing the last item from this section - remove the ENTIRE section!
[tableView beginUpdates];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
else { //just delete the row, since we've got more rows left inside this section
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];}
}
Hope, you find this helpful. Feel welcome to leave a comment.
Cheers.
