March 2013 13
Framework: MTLabel
Not too long ago I needed to build a table of contents using a table view controller. It displayed a page title and a description. Our designer asked if I could change the leading in the line spacing for each. My initial response was no that can’t be done using a UILabel because the leading is automatic based on the size and font. I started to look for solutions and the only one I could come up with was to calculate the number of characters then break the title out into multiple UILabels. After more thought it would be more difficult to detect where to break it since it would have to break after a word, not just in the middle of a word, so I continued to search for a solution. Then I found MTLabel.
Change the Leading of your Label
MTLabel is a great framework built by Michal Tuszynski. It allows you to customize line spacing, specify if you want the label to resize itself based on text height and supports almost all features of UILabel. It actually is a UIView that you sub-class with type MTLabel. This was very useful and now I’m using it all the time when I need to make wrapping text look good with the right amount of line spacing.First, you’ll need to integrate the framework into your project.
1. Download the source code from the MTLabel GitHub.
2. Add the following files into your project:
– MTLabel.h
– MTLabel.m
3. Add the “CoreText.framework” framework to your project. To do this, access your target settings, click on “Summary”, scroll down to the “Linked Frameworks and Libraries” group and click the + button to add a new framework to your project. Search for “CoreText.framework”.
4. Include the following header at the top of your class:
#import "MTLabel.h"
4. Start coding!
MTLabel *titleLabelView = [MTLabel labelWithFrame:CGRectMake(15, 4, 252, 100) andText:@"This is where you place the text you need to wrap"]; [titleLabelView setFontColor:[UIColor blackColor]]; [titleLabelView setFont:[UIFont fontWithName:@"Helvetica-Bold" size:19.0]]; [titleLabelView setLineHeight:18]; [self.view addSubview:titleLabelView];
The code above shows a basic implementation of MTLabel. Define the frame of your label and the text, then style the font color and finally set the line height to the space you would like between the lines. Look at that, a lot nicer than UILabel.
Resizing MTLabels and Table View Cells
The example above works well if you know how much text you have and the exact space you have to place the label, but what if you need dynamic text like my table of contents example. Implementing this in a table view controller is a little tougher because each title and description can be different. The method below will calculate the height of the text view needed based on the font and width provided.-(CGSize) calcLabelSize:(NSString *)string withFont:(UIFont *)font maxSize:(CGSize)maxSize{ return [string sizeWithFont:font constrainedToSize:maxSize]; }
Let’s see this method in action below. We’ll calculate the expected height that we need the MTLabel to be based on what is returned from the method above.
CGSize titlemax = CGSizeMake(250, 100); CGSize titleexpected = [self calcLabelSize:[toc name] withFont:[UIFont fontWithName:@"Helvetica-Bold" size:19.0] maxSize:titlemax]; MTLabel *titleLabelView = [MTLabel labelWithFrame:CGRectMake(15, 4, 252, titleexpected.height) andText:[toc name]]; [titleLabelView setFontColor:[UIColor blackColor]]; [titleLabelView setFont:[UIFont fontWithName:@"Helvetica-Bold" size:19.0]]; [titleLabelView setLineHeight:18]; [cell addSubview:titleLabelView];
Notice that the height of the MTLabel is being set by “titleexpected.height” which was calculated using the “calcLabelSize” method. If you are using a table view controller you’ll also need to calculate and resize the cell height based on the font as well.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ NSString *key = [[_sections objectAtIndex:[indexPath section]] sectionName]; NSArray *items = [_titles objectForKey:key]; ClassName *toc = [items objectAtIndex:[indexPath row]]; CGSize max = CGSizeMake(250, 100); CGSize expected = [self calcLabelSize:[toc name] withFont:[UIFont fontWithName:@"Helvetica-Bold" size:19.0] maxSize:max]; return expected.height; }
I hope this short tutorial helped you dynamically set the height of a text label and make it look nice with the proper line spacing.
Tutorial: Local Notifications
I know Push Notifications is all the rage lately, but let’s not forget about Local Notifications. They are much easier to implement and can sometimes be just as effective in getting Users to come back to your app. The short tutorial below will describe the steps needed to setup Local Notifications in your app.
Setup your Local Notification
The method below will set the notification to fire at a specific time. It doesn’t matter if the user has your app open or not. The notification will show up like most do on the device, usually a banner at the top of the screen that includes your app icon and a message. Users can change the alert style within “General Settings > Notifications > Your App Name”. Your notification will also appear in the device Notification Center.-(void) setLocalNotification { NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setYear:2013]; [comps setMonth:05]; [comps setDay:01]; [comps setHour:12]; [comps setMinute:0]; [comps setTimeZone:[NSTimeZone systemTimeZone]]; NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSDate *setTime = [cal dateFromComponents:comps]; UILocalNotification *localNotif = [[UILocalNotification alloc] init]; if (localNotif == nil) return; localNotif.fireDate = setTime; localNotif.timeZone = [NSTimeZone defaultTimeZone]; // Notification details localNotif.alertBody = @"Don't forget to come back to the app name today for your bonus!"; // Set the action button localNotif.alertAction = @"View"; localNotif.soundName = UILocalNotificationDefaultSoundName; localNotif.applicationIconBadgeNumber = 1; // Schedule the notification [[UIApplication sharedApplication] scheduleLocalNotification:localNotif]; }
To customize the code above change the NSDate entry to the date and time you would like your notification to fire. This method could easily be modified to take an NSDate parameter to allow you to call this method based on a dynamic date and time. Next you’ll want to change the alertBody to the message you want the user to see. It should contain a call to action so the user will be compelled to tap it to return to your app. SoundName can be changed to to accept an mp3 or wav if you wish. The example above uses the default sound setup on the users device. The application icon red badge bubble is then set to one to notify the user that their is a notification for you app that has not yet been tapped by them.
Cancelling All Notifications
You may want to include a toggle within your app to allow the user to turn off/on notifications. If the user turns notification off you should remove all notifications set so the user doesn’t receive them. Use the method below to remove all local notifications. If the user turns notifications back on then you’ll need to loop through your data recursively using the method above.// Cancel all Notifications UIApplication* app = [UIApplication sharedApplication]; NSArray *notifications = [app scheduledLocalNotifications]; if ([notifications count] > 0) [app presentLocalNotificationNow:notifications[0]];
Receiving the Notification
When a user taps on the notification they receive your app needs to handle it. irst the red bubble indicator on the app icon needs to be reset to zero. Then your app needs to handle the notification. This could be showing a specific view within your app (i.e. Give a user something free for coming back to your app if it’s a game). If you just want your app to open as normal, use the method below. This is handled in your app delegate.- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.mainVC = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil]; self.window.rootViewController = self.mainVC; [self.window makeKeyAndVisible]; // Reset the Icon Alert Number back to Zero application.applicationIconBadgeNumber = 0; // Detect the Notification after a user taps it UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; if (localNotif) { NSLog(@"Recieved Notification %@",localNotif); } return YES; } - (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif { // Handle the notification when the app is running NSLog(@"Recieved Notification %@",notif); }
Tutorial: Setting up a Jenkins Automated Build Server for iOS
A Jenkins automated build server is one of the best ways to distribute a new build of your app to stakeholders and testers during the development, QA and UAT phases of the project. Jenkins is a highly customizable solution and best of all it’s FREE! The tutorial below will walk you through setting up and configuring the server plus adding a web server to help distribute the builds to the team. Note that the build will only work on devices that are included in your application provisioning profile (just like manually installing).
Installing Jenkins
The first time I tried installing Jenkins I downloaded the software from their website. Everything was going pretty well until I linked by GitHub account to the project. Jenkins wasn’t able to find the SSH key needed to automatically download the source code. The problem was that when the standard version of Jenkins is installed it creates it’s own user on the system and runs under the name “daemon”. Since it runs under another user, not your user it doesn’t have access to your GitHub SSH key. I searched all over for a solution to this and there are a ton of people who were having the same problem. I tried changing the user that Jenkins is running on, but in order to do that I needed to switch to the Jenkins user then give access to my user on the system, but I couldn’t find the Jenkins user password. Next I tried copying the SSH keys to the Jenkins user account, but that didn’t work either.If you are like me and installed the standard Jenkins software and want to uninstall it from your system, use the commands below in Terminal to remove Jenkins from your system.
sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist sudo rm -rf /Applications/Jenkins "/Library/Application Support/Jenkins" /Library/Documentation/Jenkins sudo rm -rf /Users/Shared/Jenkins sudo dscl . -delete /Users/jenkins sudo dscl . -delete /Groups/jenkins
After a lot of searching, I found another solution. Sami Tikka created a new version of Jenkins that you can find on the jenkins-app Github that will install Jenkins under your current user account. This saved the day and made the rest of the setup for Jenkins so much easier. Once Jenkins is installed it will be hosted at http://localhost:8080, but also available publicly at http://<your IP address>:8080 so you can configure your server from anywhere you can access your IP from.
Configuring Jenkins
After Jenkins is installed we need to setup the plug-ins that we need for our server. Note that Jenkins can be used for many languages (including Java) and many source control systems (including CVS and Subversion). To access the plugins, click “Manage Jenkins” from the left side bar, then “Manage Plugins” from the list. Select the “Available” tab and check the following pluglins (Since we’re using GitHub and iOS):1. GitHub API Plugin
2. GitHub Plugin
3. XCode integration
Let’s configure the system and enter in the details needed for the plugins to work. Select “Manage Jenkins” from the left side bar, then “Configure System” from the top of the list. You’ll notice the Home Directory at the top shows that Jenkins is installed under your user account, this means that the server can access all of your SSH keys and iOS Certificates from your user account. There isn’t much to change here, all of the defaults are good. Just enter your GitHub username and email address under the “Git plugin” section. Then scroll to the bottom and under “Git Web Hook” select “Let Jenkins auto-manage hook URLs” and enter your GitHub username and password. Click “Test Credential” to ensure that the server can verify you have the correct account information.
Setup your First Job
Now that the server is ready let’s setup our first project. Click “New Job” from the left side bar. Enter the name of your application and select “Build a free-style software project” and click “OK”. Under “Source Code Management” select “Git”, then enter your Git Repository URL. This URL must be the SSH URL found on GitHub, the HTTP URL will not work because the server cannot enter in the password needed. If you have not setup an SSH key for GitHub before, check out their tutorial on Generating SSH Keys.To setup the Build Steps, click “Add Build Step” and select “XCode”. Check “Clean before build?”. Enter the “SYMROOT” to where the build will be stored (i.e. ${workspace}/build/<YourAppName>/Build/Products). Enter the “Configuration” to build the release from in your Xcode project (i.e. Debug or Release). Enter “${BUILD_NUMBER}” in the “Technical version” field. Check “Build IPA?” and “Unlock Keychain?”, then enter in your “Keychain path” as “${HOME}/Library/Keychains/login.keychain” and your password (which is your User password on your Mac). With this setup you will need to access the Jenkins server and select the “Build Now” option for the job. However if you would like to have an automated build you can setup those options under the “Build Triggers” section. It can “Build when a change has been pushed to GitHub” or “Build periodically” where it can be set to build at a specific time. An example for midnight is “0 0 * * *”. The first zero is the hour and the second is the minute to start the build.
We want to setup our server so the rest of the team can access the builds directly from their iPhone or iPad so we need to make a shell script that will copy the compiled IPA file to the web server so the team can access it. Click “Add Build Step” and select “Execute Shell”. Below is a shell script to get your started:
#!/bin/sh export OTAURL="http://:8888/ " export OTASmallIcon=icon.png export OTALargeIcon=icon.png export OTADeployPath=~/Documents/JenkinsBuilds/ export OTATitle= cp build/ /Build/Products/Release-iphoneos/*.ipa $OTADeployPath/ .ipa export info_plist=$(ls / -Info.plist | sed -e 's/\.plist//') export bundle_version=$(defaults read $WORKSPACE/$info_plist CFBundleVersion) cat << EOF > $OTADeployPath/ .plist EOF items assets kind software-package url $OTAURL/ .ipa kind display-image needs-shine url $OTAURL/icon.pn kind full-size-image needs-shine url $OTAURL/icon.png metadata bundle-identifier com. . bundle-version $bundle_version kind software title $OTATitle
You’ll notice in the script above that you need to change the values of <YOUR PROJECT NAME> throughout. The OTADeployPath should be set to the path on your MAC that you set the web server root to (see more about that below). The “bundle-identifier” should be set to the Bundle Identifier set in your Xcode project.
Setting Up the Web Server
In order for the team to access the latest build they need to access it on their device. First, install MAMP on your MAC by selecting to download “MAMP: One-click-solution for setting up your personal webserver”. This is a FREE version for personal use. Once you have installed MAMP, change the Document Root to point to where you are copying the builds to in the OTADeployPath above. Once the Document Root is set you can “Start Servers” to begin. Next, create a simple index.html file to host in the Document Root.Apps
To see your server enter http://<YOUR IP ADDRESS>:8888/ into your web browser. You should see the page above with a link to the IPA of your build.
Start Testing
The web server is setup, the automated build and shell script is set. Now let’s see if it works. Access your Jenkins server at http://<YOUR IP ADDRESS>:8080/, select your job and click the “Build Now” button from the left side bar. You’ll see the progress on the left side bar. Jenkins will attempt to access your Git repository to pull the latest code, then build it to create the IPA. Once it’s been compiled the shell script will copy the IPA to a project sub-directory under your web server document root directory. Then go to your web server URL http://<YOUR IP ADDRESS>:8888/ from your iPhone or iPad and click the link to install the app on your device. That should all work well but if you run into issues on the way be sure to check Jenkins for a reason why it failed. This information can be found under the “Build History” and clicking the “Console” icon on the right side of the table for the build that failed. Scroll down to the bottom of the output to see why it failed. Maybe it couldn’t copy the IPA correctly to your web server document root, ensure that they match.Good luck with your automated builds. Once it’s setup and working it’ll be a valuable asset to your entire team to always have the latest version of your app without you having to manually install the app on their devices each time.
Tutorial: Designing and Exporting Your App Icon
After coding your app it’s time to make that killer icon that’s going to make your app stand out from the rest in the store. There are many things to consider to designing the best icon to fit your app.
Tips to Designing the Best App Icon
1. If your app is an already established brand, then it’s best to use that brand recognition in the icon by using the logo (i.e. Facebook, Twitter, Digg, etc..). If you don’t have a big brand then your icon should represent what the app does. Instagram is a good example since it shows that it’s a camera app.2. Stay away from using text that’s longer than one word within the icon. It makes it very difficult to read when it’s sized down for the iPhone. It may read well in the 1024×1024 version of the icon, but test it on the phone to see if it’s still legible.
3. The design and colours of the icon should mimic the look of the app. It gives the experience that it’s truly a miniature representation of the app. When the user taps on the icon to open the app it should be a fluent transition of style.
4. Try to make your icon original. Since there are so many apps out there, yours may stand out if it looks different than the other ones in your category.
5. Emotion. You want your user to get an emotional connection to your app through the icon. You’ll notice that most of the top games all have a character or animal representing their game. That gives an emotional connection through the characters expression.
Icon Inspiration
Below are a couple links to collections of nice icons to give you an idea:What Do the Top 100 Paid App Icons Have in Common?
iOS Icon Gallery
Icons Feed
iOS App Icon Designs (iOSpirations)
40 Brilliant iPhone & iPad Application Icons
45+ Fantastic iOS App Icon Designs for Inspiration
Exporting your Icon
Once you have designed your fabulous icon, you’ll need to export it to all the sizes required by Apple.This is the best Photoshop template for creating iOS App Icons:
PixelReport’s App Icon Template
With this template, you can see what your icon will look like in situation, within the app store and on the springboard. Then with a simple action all icons are saved out for you including in the proper naming conventions required.
Tutorial: Setting an Event in iCal
If you would need to programmatically set an event in the users Calendar you’ll need to leverage the EventKit framework API. The EventKit framework provides classes for accessing and manipulating calendar events and reminders.
First, access your target settings, click on “Summary”, scroll down to the “Linked Frameworks and Libraries” group and click the + button to add a new framework to your project. Search for ‘EventKit.Framework’.
Then you will be able to add a method like the one below to your class.
First add the EventKit framework to the top of your *.m file.
#import
Next use the method below to set the iCal event.
-(void) setiCalEvent { // Set the Date and Time for the Event NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setYear:2013]; [comps setMonth:3]; [comps setDay:5]; [comps setHour:9]; [comps setMinute:0]; [comps setTimeZone:[NSTimeZone systemTimeZone]]; NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSDate *eventDateAndTime = [cal dateFromComponents:comps]; // Set iCal Event EKEventStore *eventStore = [[EKEventStore alloc] init]; EKEvent *event = [EKEvent eventWithEventStore:eventStore]; event.title = @"EVENT TITLE"; event.startDate = eventDateAndTime; event.endDate = [[NSDate alloc] initWithTimeInterval:600 sinceDate:event.startDate]; // Check if App has Permission to Post to the Calendar [eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { if (granted){ //---- code here when user allows your app to access their calendar. [event setCalendar:[eventStore defaultCalendarForNewEvents]]; NSError *err; [eventStore saveEvent:event span:EKSpanThisEvent error:&err]; }else { //----- code here when user does NOT allow your app to access their calendar. [event setCalendar:[eventStore defaultCalendarForNewEvents]]; NSError *err; [eventStore saveEvent:event span:EKSpanThisEvent error:&err]; } }]; }
To customize it for your app, set the date and time of the iCal event, then change the event.title to what you want the user to see in their calendar, and finally the length of the event in seconds (i.e. 600 secs = 1 hour).
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { if (granted){ //---- code here when user allows your app to access their calendar. }else { //----- code here when user does NOT allow your app to access their calendar. } }];
On iOS6, Apple introduced a new privacy control. The user can control permissions for the contact and calender by each app. So in the code side, you need to add some way to request the permission. In iOS5 or before, you can always call it without the user having to give permission.