Author Archive
Some time ago, I wrote an article on NSFileManager and how to use it for saving an image to and loading/deleting it from the documents directory. According to my stats, this is the most popular article on my blog and also comments show, that quite a few readers have been working with my sample code. I really appreciate that and would like to thank you all!
Anyways, since you guys seem to enjoy saving and loading stuff, I figured it’d be a good idea to write another tutorial on that subject. This time we’ll work with all different kinds of information (arrays, boolean values, integers, floats, images, etc…). We’ll save such information when the user exits/quits the app and we’ll load it when the app launches again.
Alright, let’s get busy, shall we? So, what do we need?
- Method for loading info right after the app did finish launching.
- Method for saving info when user exits/quits the app.
First, we navigate to our AppDelegate.h and add two methods:
//your AppDelegate.h will look something like this:
@interface AppDelegate : NSObject <UIApplicationDelegate> {
}
- (void)loadPreviousSession; //for loading
- (void)saveCurrentSession; //for saving
@end
Now, let’s add some NSMutableArray, boolean, float and an integer variable, so we have something to work with.
@interface AppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *myMutableArray;
BOOL someBool;
int someInt;
float someFloat;
}
- (void)loadPreviousSession; //for loading
- (void)saveCurrentSession; //for saving
@property (nonatomic, retain) NSMutableArray *myMutableArray;
@end
Now, we’ll implement those methods into our AppDelegate.m
@implementation AppDelegate
@synthesize myMutableArray; //don't forget to synthesize
- (void)dealloc { //a habit of mine, to insert the dealloc method, right below @synthesize. This way I don't forget to release stuff I synthesize.
[super dealloc];
[myMutableArray release]; //do proper house keeping and don't forget to release
}
- (void)loadPreviousSession {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"NameOfYourApp.dat"]; //you can choose any name for the .dat file. Usually, the name of your app is a good idea. This file will hold all our information.
NSFileManager *fileManager = [NSFileManager defaultManager];
//checks, if we already have some information stored in our .dat file
if ([fileManager fileExistsAtPath:path]) { //file exists, so there must be some information in it
NSLog(@"Data Found");
NSMutableData *theData;
NSKeyedUnarchiver *decoder;
theData = [NSData dataWithContentsOfFile:path];
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:theData]; //our decoder allows us to easily access information inside our "theData" object.
NSMutableArray *tempMutableArray; //in this example we're also working with NSMutableArray. We first create a temp version of this array and then assign its content to our "real" NSMutableArray. Why? I noticed, that directly assigning it, fails sometimes.
//now, let's load our information
tempMutableArray = [decoder decodeObjectForKey:@"myMutableArray"]; //the key is something, you can choose yourself of course. However, take a look at the - (void)saveCurrentSession method below. Your keys have to be consistent between these two methods.
someBool = [decoder decodeBoolForKey:@"someBool"];
someInt = [decoder decodeIntForKey:@"someInt"];
someFloat = [decoder decodeFloatForKey:@"someFloat"];
//now, let's assign our tempMutableArray to our myMutableArray object
[self setMyMutableArray:tempMutableArray];
//tell decoder, we're done and finally release it
[decoder finishDecoding];
[decoder release];
}
else { //file does not exist - if necessary, we can assign default values to variables in here.
NSLog(@"No Data Found");
someInt = 1;
someBool = YES;
someFloat = 1.0;
}
}
Ok, we’re half way through. Now, we need to take care of saving our stuff, in case user quits or exits the app.
Let’s add another method to our AppDelegate.m:
- (void)saveCurrentSession {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"NameOfYourApp.dat"];
//almost same story as above, but the other way round this time
NSMutableData *theData;
NSKeyedArchiver *encoder; //instead of a decoder, we create an encoder
theData = [NSMutableData data];
encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];
//let's start saving - we make a call to our encoder, followed by the type we want to encode and the key we want to assign. Important: We have to use the same keys inside our -(void)loadPreviousSession method!
[encoder encodeObject:myMutableArray forKey:@"myMutableArray"];
[encoder encodeBool:someBool forKey:@"someBool"];
[encoder encodeInt:someInt forKey:@"someInt"];
[encoder encodeFloat:someFloat forKey:@"someFloat"];
[encoder finishEncoding];
[theData writeToFile:path atomically:YES];
//release our encoder
[encoder release];
NSLog(@"successfully saved!");
}
We’re almost done! However, our methods would be pretty useless, if we didn’t call them somewhere. So, let’s take care of that
Look for the following method and add a couple lines:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//in case you're working with NSMutableArray(s), make sure to properly allocate them
myMutableArray = [[NSMutableArray alloc] init];
//load previous session
[self loadPreviousSession];
}
Finally, we have to make a call to our – (void)saveCurrentSession method.
We do that inside the following methods:
- (void)applicationWillTerminate:(UIApplication *)application { //IMPORTANT: please mind, that this method is hardly called anymore these days. Reason: multitasking (aka. fast app switching) - an app usually stays "active" in the background. Another important fact to notice is this: If your user force quits the app from the fast app switching bar, this method ISN'T CALLED either! However, this method will be called on devices, that do not support multitasking.
[self saveCurrentSession];
}
- (void)applicationDidEnterBackground:(UIApplication *)application { //user just quit the app - let's save our information
[self saveCurrentSession];
}
- (void)applicationWillResignActive:(UIApplication *)application { //app is about to become inactive. You can also make a call to - (void)saveCurrentSession in here. However, if you're also doing it inside the method above, you might trigger the method twice, which is not necessary.
[self saveCurrentSession];
}
When your app becomes active again, it might be necessary to load previously saved information. In this case, add the following method:
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self loadPreviousSession];
}
That’s it!
One more thing…working with UIImages.
If your NSMutableArray contains UIImages, you’ll run into a problem. I suggest, to convert an UIImage into a NSData object first and THEN add it to the array. This way, you won’t have any issues saving/loading it.
//we assume, we have an UIImage, named myUIImage. We want to add this image to our myMutableArray object, so we can save/load it between sessions NSData *convertedImage = UIImagePNGRepresentation(myUIImage); //add the image to the array [myMutableArray addObject:convertedImage]; //in order to assign the convertedImage to an UIImage, do something like this: myUIImage = [UIImage imageWithData:[myMutableArray objectAtIndex:0]];
Hope, you find this helpful. As usual, feel welcome to add comments, if you have questions/feedback.
A couple months ago I got myself a brand new Magic Mouse. So far I’ve been using it together with BetterTouchTool and overall experience has been really positive. However, it didn’t take that long, until I noticed some connectivity issues. Both my iMac and MacBookPro suddenly lost connection with the Magic Mouse and it took a couple seconds (sometimes even more than 10 seconds!), before the mouse reconnected and started to work again. As you can imagine, this was very annoying!
I fired up google and tried to look for some answers. Well, at least I was not alone. Many others described the same or similar issues, but there was no official response from Apple.
I tried various “solutions”, like cleaning the cache, setting Bluetooth status to hidden instead of visible, etc… Nothing really helped. One day, I had the chance to try a brand new Magic Mouse. This one didn’t have any connectivity issues and so I wanted to find out, whether mine had some sort of hardware issue and I had to send it back to Apple.
First thing I did, was replacing the batteries inside the new Magic Mouse with batteries from mine. And what happened? Bingo, Jackpot, Yeah
Connectivity issues! So, in my case it had to be the batteries. But why? I bought an expensive set, not some no name product. What was wrong with them?
Well, the batteries were alright, but the positive pole was slightly shorter. Combined with the design of the Magic Mouse and how the poles make contact inside, I believe this was the root of my connectivity issues.
Take a look at the image below:
See the difference? Battery on the left never gave me any trouble, due to its slightly bigger positive pole.
So, what can one do now? Well, actually there are two possible solutions.
1) When buying your new batteries, make sure that they got this slightly bigger positive pole.
2) Use some kitchen foil. Take a look at image below:
Cut a little piece of kitchen foil and fold it two or three times. Make sure that it’s only slightly bigger than the positive pole of your battery. Create one of these little helpers for each battery. Now put them on top of your batteries and insert them into your Magic Mouse as usual. This worked great for me and so far my Magic Mouse hasn’t lost its connection once!
Hope, this information will be helpful to someone out there
Oh, the above workaround comes without limit or warranty and please try at your own risk.
Update: I’ve tried the above workaround for about 2 weeks now and there hasn’t been a single dropped connection!
Update 2: Works still great and it has been 5 months
Sometimes, when you’re in the process of developing a new app, you might want to spice up your interface design with a little eye candy animation. Things like that are not that important to the overall functionality of your app, but they are a real treat to general usability. Even though your users might not directly notice them, they will definitely appreciate smooth transitions on a subconscious level.
Ok, let’s get to it. There are many (and also very advanced) ways to achieve animation within your app. This time I’d like to show you one of my favorite solutions, which is also the easiest one to implement.
Basic code:
CGContextRef context = UIGraphicsGetCurrentContext(); [UIView beginAnimations:nil context:context]; [UIView setAnimationCurve:UIViewAnimationCurveLinear]; //[UIView setAnimationDidStopSelector:@selector(callFunctionAfterAnimation)]; //this would call a method named - (void)callFunctionAfterAnimation; inside this class [UIView setAnimationDuration: .5]; [UIView setAnimationDelegate: self]; //animation goes here [UIView commitAnimations];
The beauty about these couple lines of code is, that you can pretty much put them into any method and they will just work. Before we take a look at some ways to implement this, let’s explore the above code a bit more in detail.
In line 3, you can set the animation curve. This will affect the way, your animation will perform. There are several different animation curve settings available:
UIViewAnimationCurveLinear UIViewAnimationCurveEaseIn UIViewAnimationCurveEaseInOut UIViewAnimationCurveEaseOut
I suggest, you try all available options and find out the difference for yourself. Usually “UIViewAnimationCurveLinear” is a good choice, but it really depends on the case. In order to see all available options inside XCode, you type UIViewAnimationCurve and then push ESC on your keyboard. XCode will then show you a list and you can simply pick the animation curve you want to try.
Line 4 (commented out above) allows you to call a method inside the same class, right after the animation has finished. This can be useful at times, when you want/need to trigger something after the animation has stopped.
In line 5, we set the duration of our animation (in seconds): 1.0 = 1 second, 0.5 or .5 = 1/2 a second.
Finally in line 7, we tell XCode what we want to animate. Let’s take a look at some examples:
Example 1:
In our class, we have a UIButton, named “OurButton”. If we want this button to become invisible, we simply call:
OurButton.alpha = 0.0;
Example 2:
Let’s say, we want to move our button from X:0, Y:0 to X:50, Y:50. So, we call:
OurButton.frame = CGRectMake(50, 50, OurButton.frame.size.width, OurButton.frame.size.height);
Example 3:
Move our button to X:50, Y:50 and also resize it to 100 (width), 100 (height). Let’s call:
OurButton.frame = CGRectMake(50, 50, 100, 100);
It’s really simple as that
Play around with different object properties and you will soon learn the advantages/limits.
One more thing…Wrappers ![]()
I hope you agree, that the above way to animate objects on your screen is quite simple to implement and use. However, if you plan on animating several different objects inside your class, I recommend, that you create one or more wrappers if possible.
Let me show you an example:
Let’s pretend, that we frequently want to turn buttons inside our class visible/invisible. Instead of writing our animation code again and again, whenever we want to change the alpha value of one of our buttons, we create the following wrapper:
- (void)setButtonVisibility:(UIButton*)theButton:(BOOL)visible {
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
//[UIView setAnimationDidStopSelector:@selector(callFunctionAfterAnimation)]; //this would call a method named - (void)callFunctionAfterAnimation; inside this class
[UIView setAnimationDuration: .5];
[UIView setAnimationDelegate: self];
theButton.alpha = (visible) ? 1.0 : 0.0;
[UIView commitAnimations];
}
To make a button, named “OurButton”, invisible, we’d simply call:
[self setButtonVisibility:OurButton:NO];
Piece of cake, right?
Good luck and let me know, if you run into any problems.
We already blogged about our live 1 on 1 coding sessions here.
If you’re interested in this service, feel welcome to visit our brand new website: www.codingsessions.com.
There you’ll find lots of detailed information on available classes, pricing, etc…
If you have questions, please do not hesitate to contact us any time.
Some time ago, we announced our 1 on 1 coding sessions. Developers all over the world have taken interest in our live tutorials and given us great feedback.
If you’re new to iPhone/iPod Touch or iPad development and you have a hard time getting started, feel welcome to contact us and we will do our very best to schedule an appointment asap.
Each live session usually lasts for one hour (give or take – we are definitely not watching the clock and we definitely don’t mind teaching a bit longer, if needed) and costs 99 USD (payment via paypal).
The great thing about this service is, that you can ask questions any time and get an immediate answer. This way, you learn much faster and get a better understanding of things that are going on in your code.
As of now, we can offer the following sessions:
- Introduction to XCode and Interface Builder
- UIViewController programming
- UIKit programming (UIButton, UITableView, UIView, UIImageView, UILabel, etc…)
- SQLite database programming
- APNS – Apple push notification service
- iOS <-> server communication programming
We also offer you to do a free screen recording of every session, so you can watch it over and over again at no extra cost.
All you need to get started, is an iChat account and contact us, so we can schedule an appointment.
Sessions are available in both english and german language.
If you have any questions, feel welcome to contact our support.
In my previous blog entry I wrote about Creating a multi page PDF document on iPhone/iPod Touch/iPad with encryption.
One of my readers described a problem, that caused his newly created PDF file to contain strange/weird characters. He actually tried to open an existing PDF document and added a watermark image to every single page.
After doing some research on the internet, I found out, that this issue is quite common. In fact, it has been around for quite some time (2 + years – give or take). Unfortunately, Apple hasn’t yet introduced a fix.
So, I did some more digging and read up on 3rd party libraries. Using those libraries, you can actually create/modify a PDF document without the above issues. Bad news is, that Apple might not approve your app. It’s really up to every single developer, whether he/she wants to go for a solution like that or not. Since I can not simply ask Apple, I decided to rule out that option.
So, what other solutions are left? Well, it certainly depends on the case, but in my and my reader’s situation, the following hack worked:
- Open the PDF-document
- Render it into an offscreen UIView
- Create a new PDF document
- Paste the image and add additional information (text/pictures)
- Save the new PDF document.
There is of course one major downside. The new PDF document no longer contains the real text, but an image displaying the text. This might turn into a problem, if zooming or copy/paste actions are needed later. However, for just reading, this works great.
For those, who want to see this in action, I’ve uploaded a demo project. Feel welcome to grab it here.
In one of my latest applications, I wanted to allow users to
A) Turn text into a PDF file (multi-page document if needed)
B) Optionally set a password to protect this very PDF file
C) Set permissions to copy/print the PDF file
Since I haven’t played with PDF content creation on the iPhone/iPod Touch/iPad before, I fired up google and discovered THIS source. It was very helpful for understanding multi-page PDF creation. However there were two problems:
- The created PDF document ignored the chosen font and size.
- It did not contain information on how to password protect the PDF file.
After playing with this code for a while, I discovered a loop hole. If you “Push” your context, you can use the following method to draw a string with the correct font/size:
UIGraphicsPushContext(pdfContext);
[myNSString drawInRect:bounds withFont:[UIFont fontWithName:fontName size:fontSize]];
UIGraphicsPopContext();
Problem (1) was solved now and after reading Apple’s documentation on PDF file creation, I had an idea how password protection might work as well.
Ok, now that I had all the ingredients, it was time to get this thing up and running.
Before you read on, please keep in mind, that part of the code below has been thrown together, in order to get useful results real quick. I somehow got a feeling, that there is (must be) a more elegant way to do this.
Anyways, here we go…
I created 2 different methods:
1:
- (void) createPDF:(NSString *)fileName withContent:(NSString *)content forSize:(int)fontSize andFont:(NSString *)font andColor:(UIColor *)color:(BOOL)allowCopy:(BOOL)allowPrint:(NSString*)password;
This method accepts the following variables:
- fileName: the name we want to assign to our PDF file (e.g. “myPDF.pdf”).
- content: the text (string) we want to write to our PDF document.
- fontSize: size of our font (9, 10, 12, 14, 16, etc…)
- font: name of the font, we want to use (e.g. “Helvetica”).
- color: the color, we want to use (black, blue, etc…)
- allowCopy: YES/NO – whether we want to allow copy for this document
- allowPrint: YES/NO – whether we want to allow print for this document
- passoword: a user defined password to unlock content of the pdf file.
2:
- (NSString *)stringToDraw:(NSString*)fontName:(int)fontSize;
This method will be called from our method (1) and will return a NSString object. More on that later…
Before we take a closer look at method (1), we need to add the following lines to the .m file (right below #import lines), we’re working in:
//defines our default PDF page layout #define LEFT_MARGIN 25 #define RIGHT_MARGIN 25 #define TOP_MARGIN 35 #define BOTTOM_MARGIN 50 #define BOTTOM_FOOTER_MARGIN 32 #define DOC_WIDTH 595 #define DOC_HEIGHT 842
We also need a NSString object, that will hold some text for us later, a BOOL value, named “done” and a NSMutableArray. Simply create those in your viewDidLoad method or wherever you initialize your class:
//in .h
NSString *tempContentString;
NSMutableArray *textArray;
BOOL done;
@property (nonatomic, retain) NSString *tempContentString;
@property (nonatomic, retain) NSString *textArray;
//in .m
@synthesize tempString;
@synthesize textArray;
- (void)viewDidLoad {
tempContentString = [[NSString alloc] init];
textArray = [[NSMutableArray] alloc] init];
}
- (void)dealloc {
[tempContentString release];
[textArray release];
[super dealloc];
}
Now, let’s assume, that we have a UITextView element, named “myTextView” and this UITextView is filled with text.
Before we call method (1), we need to prepare a few things
- (void)preparePDFCreation {
NSString *fileName = @"myPDF.pdf"; //set whatever name you like
NSString *content = myTextView.text; //this is the UITextView, containing our text.
int fontSize = 20; //set your fontSize here
NSString *font = @"Papyrus"; //set your font name here (Helvetica, Georgia, etc...)
UIColor *myColor = [UIColor blackColor]; //set your color here (blueColor, brownColor, etc...)
NSString *password = @"test"; //set your password
//prepare saving our PDF file to documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *saveDirectory = [paths objectAtIndex:0];
NSString *saveFileName = fileName;
NSString *newFilePath = [saveDirectory stringByAppendingPathComponent:saveFileName];
tempContentString = content; //we assign our content (text we want to draw to our PDF file) to the tempContentString, we created earlier
BOOL pdfAllowCopy = YES; //allow/forbid copy
BOOL pdfAllowPrint = NO; //allow/forbid print
//important: if you forbid copy or print, you definitely need to set a password. Otherwise it won't work!
done = NO;
[textArray removeAllObjects]; //clean the textArray
[textArray setArray:[content componentsSeparatedByString:@" "]]; //split our text into single words...we will find out why we do this in method (2)
//Now, let's call our method (1)
[self createPDF:newFilePath withContent:tempContentString forSize:fontSize andFont:font andColor:myColor:pdfAllowCopy:pdfAllowPrinting:password];
}
- (void) createPDF:(NSString *)fileName withContent:(NSString *)content forSize:(int)fontSize andFont:(NSString *)font andColor:(UIColor *)color:(BOOL)allowCopy:(BOOL)allowPrint:(NSString*)password {
CGContextRef pdfContext; //our pdfContext
CFStringRef path;
CFURLRef url;
CFStringRef passwordString = (CFStringRef)password;
CGRect pageRect = CGRectMake(0, 0, DOC_WIDTH, DOC_HEIGHT);
CFMutableDictionaryRef myDictionary = NULL; //the dictionary, which will later contain some important meta data, like password, etc...
const char *filename = [fileName UTF8String];
// Create a CFString from the filename we provide to this method when we call it
path = CFStringCreateWithCString (NULL, filename,
kCFStringEncodingUTF8);
// Create a CFURL using the CFString we just defined
url = CFURLCreateWithFileSystemPath (NULL, path,
kCFURLPOSIXPathStyle, 0);
// This dictionary contains extra options mostly for 'signing' the PDF
myDictionary = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(myDictionary, kCGPDFContextTitle, (CFStringRef)mainDelegate.rootViewController.writeViewController.titleTextField.text);
CFDictionarySetValue(myDictionary, kCGPDFContextCreator, (CFStringRef)mainDelegate.rootViewController.writeViewController.authorTextField.text);
if (![password isEqualToString:@""]) CFDictionarySetValue(myDictionary, kCGPDFContextOwnerPassword, passwordString);
if (![password isEqualToString:@""]) CFDictionarySetValue(myDictionary, kCGPDFContextUserPassword, passwordString);
if (!allowCopy) CFDictionarySetValue(myDictionary, kCGPDFContextAllowsCopying, kCFBooleanFalse); //kCGPDFContextAllowsCopying is set to TRUE by default
if (!allowPrint) CFDictionarySetValue(myDictionary, kCGPDFContextAllowsPrinting, kCFBooleanFalse); //kCGPDFContextAllowsPrinting is set to TRUE by default
// Create our PDF Context with the CFURL, the CGRect we provide, and the above defined dictionary
pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary);
// Cleanup our mess
CFRelease(myDictionary);
CFRelease(url);
//Now, this is a tricky part. We make use of a do - while loop in order to create as many pages as needed
do {
CGContextBeginPage (pdfContext, &pageRect); //begins a new PDF page
//create layout for our page
CGRect bounds = CGRectMake(LEFT_MARGIN,
TOP_MARGIN,
DOC_WIDTH - RIGHT_MARGIN - LEFT_MARGIN,
DOC_HEIGHT - TOP_MARGIN - BOTTOM_MARGIN);
UIGraphicsPushContext(pdfContext); //pushing the context, as explained at the beginning of this post
CGContextSaveGState(pdfContext);
CGContextTranslateCTM(pdfContext, 0, bounds.origin.y);
CGContextScaleCTM(pdfContext, 1, -1);
CGContextTranslateCTM(pdfContext, 0, -(bounds.origin.y + bounds.size.height));
if ([tempContentString length] > 0) [[self stringToDraw] drawInRect:bounds withFont:[UIFont fontWithName:font size:fontSize]]; //THIS IS THE NASTY PART
CGContextRestoreGState(pdfContext);
UIGraphicsPopContext();
CGContextEndPage (pdfContext); //ends the current page
}
while (!done);
// We are done with our context now, so we release it
CGContextRelease (pdfContext);
CFRelease(path);
}
Now, it’s getting messy. We’re about to explore the “stringToDraw” method. Before we go into detail, let me explain, what this method does.
When we draw text to our page, we have a huge problem. We do not know how much text will fit on a single page. This depends on length of the words used, font and size.
So, I figured, there must be a way to calculate this. And here is what I came up with:
Step by step…
- create a CGSize, named tempSize.
- create a CGSize, named theTextSize.
- use – sizeWithFont:constrainedToSize: method to determine, whether the text we want to draw, is too big for one page or not
- create if/else statements for results of (3)
Here is the complete method with documentation:
- (NSString *)stringToDraw:(NSString*)fontName:(int)fontSize {
CGSize tempSize;
CGSize theTextSize;
tempSize.width = DOC_WIDTH - RIGHT_MARGIN - LEFT_MARGIN; //define width of our document
tempSize.height = 10000000; //we use some unreal number, to make sure our text will definitely fit into this temporary "document"
//now we take the text we want to draw and use the method described in (3) to fit it on our temporary document
theTextSize = [tempContentString sizeWithFont: [UIFont fontWithName:fontName size:fontSize] constrainedToSize: tempSize];
//Depending on length of the text, it will fit on a single page or not.
if (theTextSize.height > DOC_HEIGHT - TOP_MARGIN - BOTTOM_MARGIN) { //the text we want to draw DOES NOT fit on a single page
BOOL pageFilled = NO;
int wordCount = 0;
float currentHeight = 0.0f;
float previousHeight = 0.0f;
NSString *returnString = [[[NSString alloc] init]autorelease];
NSString *tempReturnString = [[[NSString alloc] init]autorelease];
//Now, the following is a bit messy. However, it works
do { //repeat this loop, till our page is filled with words...
if ([textArray count] > wordCount + 1) { //if there are more words inside our textArray than the current word count + 1
returnString = (wordCount > 0) ? [returnString stringByAppendingString:[NSString stringWithFormat:@" %@", [textArray objectAtIndex:wordCount]]] : [NSString stringWithFormat:@"%@", [textArray objectAtIndex:wordCount]];
theTextSize = [returnString sizeWithFont: [UIFont fontWithName:font size:fontSize] constrainedToSize: CGSizeMake(DOC_WIDTH - RIGHT_MARGIN - LEFT_MARGIN, DOC_HEIGHT - TOP_MARGIN - BOTTOM_MARGIN)];
currentHeight = theTextSize.height;
if (theTextSize.height >= DOC_HEIGHT - TOP_MARGIN - BOTTOM_MARGIN) {
pageFilled = YES; // our page is now filled with words
}
if (currentHeight == previousHeight && currentHeight > 700.0f) { //this is a workaround. sometimes the above if statement fails and does not correctly detect a filled page. i noticed, that it happens most of the time, when using large fonts, like zapfino. so, what we do here, is simply check, if the height of our text stays the same when comparing two loops - hence "currentHeight" and "previousHeight". if that's the case and our text height is greater than 700 (look at if statement), we have a filled page.
pageFilled = YES; //page is now filled with words
wordCount --; //we subtract one word from our wordCount, because the filled page has not been correctly detected above
returnString = tempReturnString;
}
wordCount ++; //increase the wordCount
}
else {
pageFilled = YES; //we now have a correctly filled page
}
previousHeight = theTextSize.height; //set previousHeight, so we can compare it when running our next loop
tempReturnString = returnString;
}
while (!pageFilled); //repeat this loop until pageFilled = YES
for (int i = 0; i < wordCount; i++) { //remove all words, we put on our page from our textArray
[textArray removeObjectAtIndex:0];
}
if ([textArray count]) { //if there are words in our textArray
tempContentString = [textArray componentsJoinedByString:@" "]; //update our content string
}
else {
tempContentString = @"";
done = YES;
}
if ([returnString length] == 0) returnString = @" ";
return returnString;
}
else {
done = YES;
return tempContentString;
}
return tempContentString;
}
I’ve successfully implemented this code into my latest app and so far it did a great job. I managed to create PDF files with hundred pages or more in about 3-5 seconds on my iPad.
If you find this source helpful, manage to improve it or have questions/feedback, please do not hesitate to leave a comment.
[UPDATE: I've created a little demo project, showing this code in action. Grab it here.]
If you’re developing an iPhone, iPod Touch or iPad app and get stuck, the internet definitely is your friend. You can find lots of helpful resources on different coding forums or sites like this one.
However, sometimes the information there is just not enough or you might have an idea and don’t know how to get started.
Well, this is where our new service might come in handy.
As of now, you can schedule a 1 on 1 session with one of our coders and he/she will help you.
Sessions will be held via iChat and our coder will share his/her screen with you. This way, you can exactly see what is going on and therefore learn much faster. If you like, we’ll also do a screen recording for free, so you can watch it later again and again at no extra cost.
Some session examples:
* Your first app: How to build proper view hierarchy.
* SQLite database programming.
* Server communication – How to submit and retrieve data from a webserver.
* In app purchases.
* Push notifications.
* 3D game development
* etc…
Our live coding sessions are available in both english and german language.
If you’re interested, just send an email to lindmandesignsupport[at]me.com and we’ll provide you with details on pricing, etc…
I recently discovered, that push notifications containing the word “(null)” (without the quotes) do not get pushed. They don’t break your APNS connection, simply never reach the recipient’s device.
Most likely, Apple wants to avoid some injection and I’m pretty sure, there are more words out there, that will not push.
In PHP, you can take simple precautions, to work around this:
$message = "(null) bla bla bla bla bla"; //this is our message
$messageFiltered = str_replace("(null)", "", $message);
I discovered this, when trying to push an iCal calendar event. Sometimes, iCal does not add the sender’s name, but (null) instead.
Hope, this is helpful to someone out there.
In case, you know more “forbidden words”, please leave a comment.
When implementing a push notification feature into your application, you usually create two different types of certificates: One for development (sandbox) and one for your customers/testers (production).
As you might already know, your device will automatically run in sandbox mode when launching the app directly from XCode.
So, the following method inside your app delegate will receive a device token, which is different than the one it receives, when you install it via iTunes:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken;
You have to keep in mind, that your push messages will not get delivered on this device, if you’re not running the development (sandbox) certificate on your server. Another important thing is the fact, that in case your server is running the production certificate and you try to send a push message to a device in sandbox mode, it will break connection to APNS. This means, that even push messages using the correct certificate will not get delivered any longer, until you reset connection to APNS.
So, if you sometimes wonder why push notifications fail during development of your app, consider the above.


