Thursday, September 29, 2011

Deploying the app to the real device

Ok, well, the time has come to get the app onto the device - an iPod touch. I want to start showing it around.

I've done it before, so, it's do-able. It's just a question of figuring it out again. I wonder if I made a blog entry on it? Well, I did when I was deploying it for a client, so this will be *less* complicated. But I think I'll just take another look at it.

Ok, let's start from here:

https://developer.apple.com/ios/manage/overview/index.action

Ok, after I log in to the iphone developers portal, which is my inititials @ mycompany.com, I see the familiar menu on the left.

Well, I have a profile (or is it iOS development certificate - or are they the same?) for the previous app, but not for this one.

In the ‘Certificates’ section of the iOS Provisioning Portal, you can request individual iOS Development Certificates. All iOS applications must be signed by a valid certificate before they can be run on an Apple device. In order to sign applications for testing purposes, Team Members need an iOS Development Certificate.



I just want the individual certificate.

Let's check out the "How To" under "Certificates". It looks like I'm at the "Configure Profile" with a check under "Development Certificate".

A digital identity is an electronic means of identification consisting of a secret "private key" and a shared "public key". This private key allows Xcode to sign your iOS application binary.



Ok. So, the certificate is a private key?


The digital certificates you request and download are electronic documents that associate your digital identity with other information, including your name, email address, or business. An iOS Development Certificate is restricted to application development only and is valid for a limited amount of time. The Apple Certification Authority can also invalidate ("revoke") a certificate before it expires.




Generating a Certificate Signing Request

To request an iOS Development Certificate, you first need to generate a Certificate Signing Request (CSR) utilizing the Keychain Access application in Mac OS X Leopard.

// Ok, to *get* the dev certificate, you need to *ask* for it (create a CSR) - using the KeyChain Access app.

The creation of a CSR will prompt Keychain Access to simultaneously generate your public and private key pair establishing your iOS Developer identity. Your private key is stored in the login Keychain by default and can be viewed in the Keychain Access application under the ‘Keys’ category. To generate a CSR:


I'm not clear if you need a key for each app or not. Working theory is yes.



To generate a CSR:

In your Applications folder,

open the Utilities folder and launch

Keychain Access.

In the Preferences menu, set Online Certificate Status Protocol (OSCP) and Certificate Revocation List (CRL) to “Off”.


Well, when I bring that up, I have both an iPhone developer and an iPhone distribution certificate under "certificates" and they don't expire till March 2012.

I also have two keys for developer and distribution.

I also have "MyCertificates" for developer and distribution.

One caveat is that the name for the key insignia for the certs is different for developer and distrubution.

Actually, I just need to test right now.

What if I just change to "device" instead of "simulator" under XCode 4?

Ah it says "Code Sign error: The identity 'iPhone Developer' doesn't match any identity in any profile".


Ok, let's look at this and see if it helps:

http://mobiforge.com/developing/story/deploying-iphone-apps-real-devices

In order to test your iPhone applications on your device, you need to obtain an iPhone Development Certificate from the iPhone Developer Program Portal. This needs to be done once for every device you wish to test your apps on. The following sections walk you through the various steps, from obtaining your certificate, to deploying your applications onto the device.



First, obtain the 40-character identifier that uniquely identitfies your iPhone/iPod Touch. To do so, connect your device to your Mac and start Xcode. Select the Window > Organizer menu item to launch the Organizer application. Figure 1 shows the Organizer application showing the identifier of my iPhone. Copy this identifier and save it somewhere. You will need it later on.


I think I've done this. But, I might just want to walk through the whole thing. Wait, I did load this app before on my device!

If I open window / organizer, I see it under applications. But, this is a new project, but I have an an app id.

Anyway, here's my device identifier:

b29130c9477fea6abf766f9e7258f7818d3f643d

Ok, the provisioning profile, my client's, is expired.

Ok, I don't know where the profile I generated is.

Ok, let's just walk through the steps - again.

Generating a Certificate Signing Request

Before you can request a development certificate from Apple, you need to generate a Certificate Signing Request. This step must be performed once for every device you wish to test on. To generate the request, you can use the Keychain Access application located in the Applications/Utilities/ folder (see Figure 2).


In the Keychain Access application, select the Keychain Access > Certificate Assistant menu and select Request a Certificate From a Certificate Authority (see Figure 3).


Ok, I'm changing the default address to my company email address.

In the Certificate Assistant window (see Figure 4), enter your email address, check the Saved to disk radio button and check the Let me specify key pair information checkbox. Click Continue.



Choose a key size of 2048 bits and use the RSA algorithm (see Figure 5). Click Continue.


You will be asked to save the request to a file. Use the default name suggested and click Save (see Figure 6).



Logging in to the iPhone Developer Program Portal

Once you have generated the certificate signing request, you need to login to Apple's iPhone Dev Center (see Figure 7). Click on the iPhone Developer Program Portal link on the right of the page. Remember, you need to pay US$99 in order to access this page.



In the iPhone Developer Program Portal page, click the Launch Assistant button (see Figure 8) to walk you through the process of provisioning your iPhone and generating the development certificate.



First, you will be asked to create an App ID (see Figure 10). An App ID is a series of characters used to uniquely identify an application (or applications) on your iPhone. You only need to create an App ID once per application, i.e. you do not need a new App ID for new versions of your app. Enter a friendly name to describe this App ID (to be generated by Apple). Click Continue.


I'll give it a different name "JlptVocabQuiz"

The next screen allows you to provide a description of your iPhone/iPod Touch. You need to provide the device ID that you have obtained earlier (see Figure 11). Click Continue.


Ok, good - it's got my iTouch on file and is letting me select it.

You are now ready to submit the certificate signing request to Apple (see Figure 12). The instructions on the screen show you the steps that you have performed earlier. Click Continue.


And - it's got my existing certificate on file. Great! I can just use this. Next...

Provide a description for your provisioning profile (see Figure 14). A Provisioning profile will be generated so that you can download it at a later stage and install it on your device. Click Generate.


I'll call it JlptVocabQuizProfile

Drag and drop the downloaded Provisioning profile (in the Downloads folder) onto Xcode (located in the Dock). This will install the Provisioning profile onto your connected iPhone/iPod Touch. Click Continue (see Figure 17).


Ok, going smoothly. The downloaded file name is:


JlptVocabQuizProfile.mobileprovision


Good - it's now in "Window", "Organizer", "ProvisioningProfile".

You can verify that the Provisioning profile is installed correctly on your device by going to the Organizer application and viewing the Provisioning section (see Figure 18) to see if the profile has been added.


Right. Now what?

Back in the iPhone Developer Program Portal, you are now ready to download and install the development certificate onto your iPhone/iPod Touch. Click the Download Now button (see Figure 19) to download the development certificate to your Mac. Click Continue.


I'm using an existing one, I think - let's see what it does.


Here's what the Assistant says:

Step 3: Verify your private and public keys in Keychain Access

Verify the iOS Developer private and public keys are paired together in the Keychain Access application to ensure your Certificate is properly configured on your Mac.


Ok, it's the same one I had before - when I click on the downloaded development certificate, it just brings up the certificate in keychain access - they one with the private key under my company name, not my personal name.


Back to the web page instructions:

In the Keychain Access application, select the login keychain and look for the certificate named "iPhone Developer:" (see Figure 22). If you can see it there, your certificate is installed correctly.


Same thing.

You are now almost ready to deploy your iPhone application onto your iPhone/iPod Touch. Click Continue (see Figure 23).



In Xcode, under the Active SDK item (if this item is not already on the toolbar, go to View > Customize Toolbar and add it to the toolbar), select the OS version number of the device that is currently connected to your Mac. In my case, my iPhone is running the older iPhone OS 2.0, hence I selected "iPhone Device (2.0)" (see Figure 25).


Well, I couldn't find that. I'm just going to click run - and it works. Phew. Well, sort of. I think the background might be covering up the display. But that's and issue for another post.

Wednesday, September 28, 2011

Getting the background down - UITable View

Today, we're going to add a title and some links and stuff to the cover page, and then take a snapshot of it and turn it into a splash page. I might also add a button for "credits" or something, because I think on the Android app, they made it too crowded.

So, here's what the start page looks like right now:



I think I'll just start out with taking a look at the Android app. The first one is a title, which says "JLPT Vocabulary Quiz". Ok, that's just adding a label. I also want to put the current JLPT level up in the title when you're taking the quiz. Here's how you do that:


self.title = @"JlptQuizApp";


So, I'll change that to "JLPT Vocab Quiz Level", plus the level.

So, what I want to do is append the level, which I can get from prefs, to that setting. How do you append to a string in Objective C? Well, I know it's an NSString, so, let's start with that.

From this thread:

http://forums.macrumors.com/showthread.php?t=467099

It looks like this is the recommended way:


NSMutableString *ms = [[NSMutableString alloc] initWithString:@"Hello"];
[ms appendString:@"World"];



Ok, great, it's working. Here's the code:

NSMutableString *myTitle = [[NSMutableString alloc] initWithString:@"JLPT Level "];

NSInteger jlptLevel = [Utils getJlptLevel];

NSString *jlptLevelStr = [NSString stringWithFormat:@"%d", jlptLevel];

[myTitle appendString:jlptLevelStr];

[myTitle appendString:@" Vocab Quiz"];

self.title = myTitle;



Next, we don't need the "back" navigation button. I already got rid of this somewhere.

Here it is:

- (void) viewWillAppear:(BOOL)animated{
self.navigationItem.hidesBackButton = YES;
}


Good - this page looks good now, at least in its current state:



Now, something tricker - the dreaded find the right color for the highlights. I don't like the default red and green - the seem to bright. I just want to spend a few minutes messing around with them to figure out a subtler shade. But, the only way to do that is to use rgb to set the color, which I'm not sure I can do yet. Let's see the code where we're current setting the color:

// setting the the selected cell as incorrect
UITableViewCell *incorrectCell = [tv cellForRowAtIndexPath:indexPath];
incorrectCell.backgroundColor = [UIColor redColor];




So, it's the UIColor class I should take a look at.

Ok, so this thread has some good suggestions:

http://stackoverflow.com/questions/1956587/how-do-i-make-my-own-custom-uicolors-other-than-the-preset-ones

I'm gonna try this one:

[UIColor colorWithRed:26.0f/255.0f green:131.0f/255.0f blue:32.0f/255.0f alpha:1.0f];



Ok, let's pick something from this handy url:

http://cloford.com/resources/colours/500col.htm

How about "orangered 1":

well, it's better, if I give an alpha of .5. But I just noticed there's some kind of color overlap in the margin. We'll tackle that later, let's just pick out the green color for now.

Let's try emrald green:


emeraldgreen emeraldgreen #00C957 0 201 87 5753088


Ok, here's what I ended up with:




Not bad, huh? If you look closely, there's a little bit of a different shade on the right and left side of the colored bars. I have no idea where that's from, but if I keep the alpha value relatively high, it's not really noticable.

Here's the ode:

if(result) {
NSLog (@"correct");
appState.correctAnswers++;
}
else {

NSLog (@"incorrect");

// setting the the selected cell as incorrect
UITableViewCell *incorrectCell = [tv cellForRowAtIndexPath:indexPath];

// colors from http://cloford.com/resources/colours/500col.htm

// brown1
incorrectCell.backgroundColor = [UIColor
colorWithRed:255.0f/255.0f
green:64.0f/255.0f
blue:64.0f/255.0f alpha:1.0f];


}


NSUInteger correctRow = [appState slotOfCorrectAnswer];

NSIndexPath *correctAnswerPath = [NSIndexPath indexPathForRow:correctRow inSection:0];

UITableViewCell *correctCell = [tv cellForRowAtIndexPath:correctAnswerPath];

// colors from http://cloford.com/resources/colours/500col.htm

// springgreen
correctCell.backgroundColor = [UIColor colorWithRed:0.0f/255.0f green:255.0f/255.0f blue:127.0f/255.0f alpha:.9f];

Monday, September 26, 2011

In this post, we're going to continue walking through the UI, getting it to look as much like the android version as we can. One of the things we just did was to set the "English" label to "clear". This results in UI that looks like this:



Ok, we need to slice off that raggedy edge. Let's use gimp to just crop that - tools, transform, crop.

That's better. And we still keep the rough edges on the top and bottom for that ragged look and feel.




So, clearly the button stands out. So we want to make it clear, no I don't think so. What did we do with the Android version?

Ok, it's semi-transparent, with border.

Hm, running out of space, only 914.1 mb left on the "start" hard drive. Empty the trash - now 14G. That'll do for now. It still seems like not much space.

Anyway - how do we make the button look better, like it fits the background? What I did in the android app was to just make it semi-transparent, I think. No, if I change the alph to like 50, it fades the font, too, and we dont' want that. Ok, maybe I just should set the background color to something. Let's just try brown for now.

Well, on the interface builder, you only get the circle-like thing which only lets you pick a color without knowing the rgb value as far as I know, and if there's one thing I want to know, it's the rgb color. Maybe there's a way, but you can't just pick "brown". So, I'll set it in the code.

So, here are the outlets:


IBOutlet UIButton *quizButton;
IBOutlet UIButton *settingsButton;


Ok, now, let's set it in the viewDidLoad.

[quizButton setBackgroundColor: [UIColor brownColor]];

Well, no. Doesn't work. According to SO:

I assume you're talking about a UIButton with UIButtonTypeRoundedRect? You can't change the background color of that. When you try changing it's background color you're rather changing the color of the rect the button is drawn on (which is usually clear). So there are two ways to go. Either you subclass UIButton and overwrite its -drawRect: method or you create images for the different button states (which is perfectly fine to do).


So, it's inherited from the rect. Ugh, I don't want to deal with creating images for this.

However, the same thread has a suggestion:


loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
[loginButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
loginButton.backgroundColor = [UIColor whiteColor];
loginButton.layer.borderColor = [UIColor blackColor].CGColor;
loginButton.layer.borderWidth = 0.5f;
loginButton.layer.cornerRadius = 10.0f;


edit: of course, you'd have to #import



No, doesn't work.

Ok, what the next trip up my sleeve? Arduously create the images? Subclass UIButton? Let's look at the nib again.

Nope, that only affects the corners.

Well, I could get into gimp, and just create a single image. How big would it have to be? Does it make a difference? I don't care about the various states, I just can use one image for them all. But, if I instantiate it, does it replace the instantiation of the button on the nib?

I just ran across a post for setting the font color, which looks like it might also not be trivial:

[myButton setTitleColor:[UIColor colorWithRed:100.0 green:100.0 blue:100 alpha:1.0] forState: UIControlStateNormal];


Nope. That doesn't work either. Man.

Oh, my question is, what is the impact of the button already being on the nib?

Let's try this code:



UIButton button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:@selector(aMethod:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:@"Show View" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[view addSubview:button];



What is the view in my case?

Ok, forget all this. I will just throw any old view in there now.

[settingsButton setBackgroundImage:[UIImage imageNamed:@"blossoms.png"] forState:UIControlStateNormal];
[settingsButton setBackgroundImage:[UIImage imageNamed:@"blossoms.png"] forState:UIControlStateDisabled];
[settingsButton setBackgroundImage:[UIImage imageNamed:@"blossoms.png"] forState:UIControlStateHighlighted];


Good Lord. Even this buys me nothing.

Here's more saying use the image:

Use setImage:forState:

- (void)setImage:(UIImage *)image forState:(UIControlState)state

Also, don't forget to create your button with [UIButton buttonWithType:UIButtonTypeCustom]


Ok, maybe I just need to try that last piece.

settingsButton = [UIButton buttonWithType:UIButtonTypeCustom];

[settingsButton setBackgroundImage:[UIImage imageNamed:@"plum_blossoms.png"] forState:UIControlStateNormal];
[settingsButton setBackgroundImage:[UIImage imageNamed:@"plum_blossoms.png"] forState:UIControlStateDisabled];
[settingsButton setBackgroundImage:[UIImage imageNamed:@"plum_blossoms.png"] forState:UIControlStateHighlighted];


Nope. Ok, it looks like I'm going to have to create this programmatically i I am to do any customization. Well, this is something I'll need to learn anyway.

So, here's something from SO:





UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:@selector(aMethod:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:@"Show View" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[view addSubview:button];


Still nothing. Sigh. This is turning into a real hassle. Maybe it's in the wrong method? How about viewWillAppear? No, it's successfully loading the background image.

Ah - at last. This code does something:


UIButton * btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(0, 0, 100, 50);
[btn setTitle:@"Hello, world!" forState:UIControlStateNormal];
[self.view addSubview:btn];



And you can see it below:



I think it was the self.view that made the difference.

But, as has been noted before, it's not so simple:

if you want to change the background color of a UIButton you have to use UIButtonTypeCustom type instead of UIButtonTypeRoundedRect



But, if I do this:


UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(0, 0, 100, 50);
btn.backgroundColor = [UIColor brownColor];

[btn setTitle:@"Hello, world!" forState:UIControlStateNormal];
[self.view addSubview:btn];


I don't get the rounded rectangle! Oy vey - I really should give up on all this customizing.

So, now we're struggling. I will need to create a rounded rec image, won't I?

Wait, there's something else I can try:

from http://stackoverflow.com/questions/372731/how-can-i-set-a-button-background-color-on-iphone

downloadButton = [[UIButton alloc] initWithFrame:CGRectMake(36, 212, 247, 37)];

downloadButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
downloadButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

[downloadButton setTitle:NSLocalizedStringFromTable(@"Download", @"Localized", nil) forState:UIControlStateNormal];
[downloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[downloadButton setFont:[UIFont boldSystemFontOfSize:14.0]];

UIImage *newImage = [[UIImage imageNamed:@"greenButton.png"] stretchableImageWithLeftCapWidth:12.0f topCapHeight:0.0f];
[downloadButton setBackgroundImage:newImage forState:UIControlStateNormal];

[downloadButton addTarget:self action:@selector(downloadNewItem) forControlEvents:UIControlEventTouchDown];

downloadButton.backgroundColor = [UIColor clearColor];
[downloadDisplayView addSubview:downloadButton];


Well, this is still using an image - which I think all I need is a rounded rec image.

Ok in the same thread, this is recommended to create buttons:

https://github.com/dermdaly/ButtonMaker

Ok, let's download this app.

Ok, it doesn't run - it looks like it needs some frameworks and a target.

How to had frameworks?

http://stackoverflow.com/questions/3352664/how-to-add-existing-frameworks-in-xcode-4

Here's how:

In the project navigator, select your project
Select your target
Select the 'Build Phases' tab
Open 'Link Binaries With Libraries' expander
Click the '+' button
Select your framework
(optional) Drag and drop the added framework to the 'Frameworks' grou


Well, I can't find the target, or if I do, I dont' see any "build phases" tab.

Well, how about the "selected run destination is not valid for this action"?


On SO, the answer is this:


I had that issue several times. Basically, just set the SDK to MacOS X 10.6 and it should work properly



So, how to you set the base sdk?

Ah there it is, under "product" , you have to select "project", and you'll see the build settings tab.

Ah, good - it ran, but couldn't find a couple of frameworks. How to add them?

Let's try this again:


In the project navigator, select your project
Select your target
Select the 'Build Phases' tab
Open 'Link Binaries With Libraries' expander
Click the '+' button
Select your framework
(optional) Drag and drop the added framework to the 'Frameworks' group


Ah, there's target - got it. It underneath "project":



What? My CoreGraphics.foundation and UIkit.foundation aren't found? Doesn't make sense. Finder can't find s**t.

Well, if I go to another project, I can find those to, And make a reference (not copy) using this technique from SO, the same thead:

Ok, I finally did it, as follows: 1) In the "project navigator", open the "frameworks" folder and select one the existing frameworks (e.g. UIKit.framework) 2) Right click and select "Show in Finder" from the menu 3) From the newly opened folder in the finder, drag the framework folder you are interested in (e.g. OpenGLES.framework) into the "frameworks" folder in XCode 4) Be sure not to "copy items into destination's group folder" 5) Choosing "Create groups for any added folders" seems to make it –



And now XCode is hung. I give. Downloading that project was a rathole. Probably due to the new xcode version.

Ok, let's just grab a rounded reg from the net, and change the color. I'll pick this up in the next post.

Saturday, September 24, 2011

Centering the text in a section header - not trivial!

Ok, just a quick post on changing the background color on a table header. I'm going to rely on the Android version for the color. Let's see if we can figure out what went on with that.

Ok, just looking at it and comparing, I want to do 3 things with the header (section header, I think). The first is to make it transparent. The second is to change to to the same font color as on the Android. The third is to center it.

I also want to change the font color for the table rows, and make them transparent.

This post

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5172-font-size-color-tableview-header.html

talks about changing the section header.

It's very similar to code I already have:

(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

// Create label with section title
headerLabel = [[[UILabel alloc] init] autorelease];
headerLabel.textAlignment = UITextAlignmentCenter;
headerLabel.frame = CGRectMake(20, 6, 300, 30);
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textColor = [UIColor brownColor];
headerLabel.font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:45.0];

// support line break mode for multiline
headerLabel.lineBreakMode = UILineBreakModeWordWrap;

// 0 means any number of lines - necessary for multiline
headerLabel.numberOfLines = 0;

headerLabel.text = currentQuestion.questionKanji;

// very important!
[headerLabel sizeToFit];

// Create header view and add label as a subview
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, SectionHeaderHeight)];
view.backgroundColor = [UIColor clearColor];
[view autorelease];

[view addSubview:headerLabel];

return view;
}


So, it create a label, adds it as a subview to view, and then returns the view to be displayed. I've changed the color to brown for now, and changed the background color to clearColor on both the label and the view. Let's check it out.



Ok, we've made some progress. I'm not sure why it's not completely clear - is there an alpha factor at work? I was mucking around with that yesterday. But first, let's center the text.

Well, if I get rid of the size to fit, it centers - but becomes much smaller.

I saw this class posted on SO:

@interface UILabel (BPExtensions)
- (void)sizeToFitFixedWidth:(CGFloat)fixedWidth;
@end

@implementation UILabel (BPExtensions)


- (void)sizeToFitFixedWidth:(CGFloat)fixedWidth
{
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, fixedWidth, 0);
self.lineBreakMode = UILineBreakModeWordWrap;
self.numberOfLines = 0;
[self sizeToFit];
}
@end


I'm not sure about the self.frame - but I can hardcode it for now. But it's just doing the same type of things I'm doing. It's not setting it to centered.

This is a tricky one. The label frame is created the CGRectMake, which has hardcoded numbers.

Here's some info on CGRectMake and CGSize, which are somehow unavailable in Apple's documentation, possibly because they come from C originally or something:

http://iphonedevelopertips.com/c/cgrect-cgsize-and-cgpoint.html

Digging into development of iPhone applications, you’ll eventually encounter references to CGRect, CGSize, and CGPoint. These references are to C structures (see this post for more information on structures). This post will provide a high-level view of what comprises CGRect and its counterparts. Here is how CGRect is defined:


struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CGRect CGRect;


Ah hah. So, CGRect contains CGSize.

Going a little further, we can find that CGPoint and CGSize are defined as follows:



struct CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CGPoint CGPoint;

struct CGSize {
CGFloat width;
CGFloat height;
};
typedef struct CGSize CGSize;


Ok - so CGSize is the height and width.

So, if I use this code from my "tableView:heightForHeightInSection" method:

NSString * labelText = currentQuestion.questionKanji;

// set a font size
UIFont *labelFont = [UIFont fontWithName:@"Helvetica" size:30.0];

// get a constraint size - not sure how it works
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);

// calculate a label size - takes parameters including the font, a constraint and a specification for line mode
CGSize labelSize = [labelText sizeWithFont:labelFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];


This will give me the height and width for my frame.

So after the code above, I can do this:


float labelHeight = labelSize.height;
float labelWidth = labelSize.width;


Then do this:

headerLabel.frame = CGRectMake(20, 6, labelHeight, labelWidth);


Well, the part of the problem was that height I was setting for the font (for the UILabel) in the tableView:viewForHeaderInSection was different than that which I was using in the tableView:HeightForHeaderInSection part. So that's fixed by changing the font size to be 35 for both, and use the calculated label height when creating the rect:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

CGSize labelSize;
labelSize = [self getLabelSize];

float labelHeight = labelSize.height;
float labelWidth = labelSize.width;


// Create label with section title
headerLabel = [[[UILabel alloc] init] autorelease];
headerLabel.textAlignment = UITextAlignmentCenter;


headerLabel.frame = CGRectMake(20, 6, 250, labelHeight);


headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textColor = [UIColor brownColor];
headerLabel.font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:35.0];
// support line break mode for multiline
headerLabel.lineBreakMode = UILineBreakModeWordWrap;


headerLabel.text = currentQuestion.questionKanji;

// Create header view and add label as a subview
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, SectionHeaderHeight)];
view.backgroundColor = [UIColor clearColor];

[view autorelease];
[view addSubview:headerLabel];

return view;
}


And here's the method to calculate the label size:


- (CGSize) getLabelSize {
NSString * labelText = currentQuestion.questionKanji;

// set a font size
UIFont *labelFont = [UIFont fontWithName:@"Helvetica" size:35.0];

// get a constraint size - not sure how it works
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);

// calculate a label size - takes parameters including the font, a constraint and a specification for line mode
CGSize labelSize = [labelText sizeWithFont:labelFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
return labelSize;
}



And here's where we'll leave it for now:

Friday, September 23, 2011

Adding a background image - UIImage and UIImageView


Ok, time to clean up a bit of the display. I've been itching to get at this for a while. The *very first thing* I'm going to do is center some of the text.

Here's an example of how to center text:


headerLabel.textAlignment = UITextAlignmentCenter;


Although, that one is showing up on the left, for some reason. Let's try it with a different label. How about the "english" field?

We'll try this:


english.textAlignment = UITextAlignmentCenter;


in this method:


- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath


Yes, that worked. I also reduced the size of the button. But, the background color of the label and the surrounding area are both gray. Actually, I've been thinking about setting a background images, similar to the android app.

We can try something like this from SO:

Add it UIViewController's view, somewhat like this:

- (void) viewDidLoad
{
UIImage *background = [UIImage imageNamed: @"background.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage: background];

[self.view addSubview: imageView];

[imageView release];

[super viewDidLoad];
}


I can just kind of copy copy and past it, more or less.

No, it doesn't work. It's covering up the contents of the view.

Actually, how is the splash page doing it? Ok, that's just an image - the text is part of it. It's called default.png.

Here's some more advice:

UIViewControllers don't have background images. Only views themselves have visual attributes.

UIView does not have a background image property. To display a background image, you usually simply put a UIImageView displaying the image in the view hierarchy so that it appears visually behind all other views. This can be done programatically or in Interface Builder.



But, no examples for doing it in code. What about the IB?

Ok, first I have this code:

UIImage *background = [UIImage imageNamed: @"background.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage: background];

[self.view addSubview: imageView];

[imageView release];


and also in my NextLevel controller, I have this code:

UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}


So, it looks like it's not allocating or initializing the image view, so I assume that's being taken care if in the IB.

Now, how is imageView declared?

@interface NextLevelViewController : UIViewController {
UIImageView *imageView;
UILabel *art_title;
UILabel *url;

}


@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UILabel *url;
@property (nonatomic, retain) IBOutlet UILabel *art_title;


Ok, I have it set up in the variables and the declarations. Now, I just need to put the code in onViewLoad:




// UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];
UIImage *img = [UIImage imageNamed: @"background.png"];


if (img != nil) { // Image was loaded successfully.
[backgroundView setImage:img];
[backgroundView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}



Ok, well, it works, but again the image is displayed on top of the controls. I there a z-order or something?

Ok, it's editor, arrangement, then send to back if using the nib.

Actually, the original method might be easier, since I don't have to set anything up in the nib. Ok, it's the "sendSubViewToBack" method that does the trick:


UIImage *background = [UIImage imageNamed: @"plum_blossoms.png"];
UIImageView *backgroundView = [[UIImageView alloc] initWithImage: background];

[self.view addSubview: backgroundView];

[self.view sendSubviewToBack:backgroundView];

[backgroundView release];


Here's what it looks like:




I'm not totally happy - the metallic blue of the nav bar doesn't blend that well with the background. I wonder what my options are? I don't want to get into a whole thing with overhauling the UI, but I don't want it to be boring either.

Advancing to the next level - using prefs.

Ok, after nailing down the transition between the start, quiz and next level display, we're going to now advance the start and end quiz numbers on a successful completion of a quiz (i.e. all the questions in a sequence, e.g. 1-10, 10-20, etc. are completed).

Actually, the "next level" display is brought up when this occurs, so it's a perfect place to advance the start and end quiz numbers.

So, on the NextLevelViewController, I'll call a new utils method;


[Utils incrementQuizRange];


Here are the utils methods:

+ (void) incrementQuizRange {

NSInteger startNum = [Utils getStartNum];
NSInteger endNum = [Utils getEndNum];
NSInteger diff = endNum - startNum;

startNum = endNum + 1;
endNum = endNum + diff + 1;

[Utils setStartNum: startNum];
[Utils setEndNum: endNum];

}

+ (void) setJlptLevel:(NSInteger)jlptLevel
{

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

[prefs setInteger:jlptLevel forKey:@"jlptLevel"];

[prefs synchronize];

}

+ (void) setStartNum:(NSInteger) startNum
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

[prefs setInteger:startNum forKey:@"startNum"];

[prefs synchronize];

}

+ (void) setEndNum:(NSInteger) endNum
{


NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

[prefs setInteger:endNum forKey:@"endNum"];

[prefs synchronize];

}






- (IBAction)openQuestionController
{
NSLog(@"openQuestionController");


// todo - check how state was orignally saved

// reset index of shuffled array to zero
appState.shuffledArrayIndex = 0;



NSInteger startNum = [Utils getStartNum];
NSInteger endNum = [Utils getEndNum];


// create an array of keys to the list of question
// this will only contain keys starting from start num and a
// ending with end number
NSMutableArray* questionDictKeys = [NSMutableArray array];

// loop from start number to end number
for (int i = startNum; i <= endNum; i++){

// convert the integer to an object
NSNumber *myKey = [NSNumber numberWithInt:i];

// add the number to the list of keys
[questionDictKeys addObject:myKey];
}


// shuffle the list of keys and put them
// into the app state list of shuffled keys
appState.shuffledQuestionDictKeyList = [questionDictKeys shuffledArray];

// print the keys for debugging
for (int i = 0; i < [appState.shuffledQuestionDictKeyList count]; i++){
NSLog(@"shuffledQuestionDictKeyList[i]: %d", [[appState.shuffledQuestionDictKeyList objectAtIndex: i] intValue]);

}

// reset correct answers to zero
appState.correctAnswers = 0;

// send control to the question controller
questionController = [[QuestionController alloc] init];

[self.navigationController pushViewController:questionController animated:YES];

[questionController release];

}



Actually, the debugger is showing the end number as 2, when it should be like 205 or something. What happened?

Oh. Here's what I meant to do in the end quiz number calculation:



endNum = endNum + diff + 1;



Ok, now let's try it.

Ok, one more thing - we've got to fix the bad data. We can do that with the settings display.

And, voila - that part is complete.

Thursday, September 22, 2011

How to push and pop from the stack; bad access and trapping zombie calls

I'm having a problem with the NextLevelController. For some reason the "pop" method isn't working. It's just not popping back to the Start Controller when the button is pressed. But, the exact same functionality works for a different - going to the settings view and back. The only difference is how they're called. One (settings) is the result of a button push. The other is if a certain condition is met in the viewDidLoad method. What if I added a button to the StartController to see if that would work?

Well, I tried that, but still no luck. The method is not getting called, although I connected to the button.

Btw, here's how to do text alignment for the next time I notice that problem.

// align display labels
pctCorrect.textAlignment = UITextAlignmentRight;
correctSoFar.textAlignment = UITextAlignmentRight;
remaining.textAlignment = UITextAlignmentRight;
questionsAsked.textAlignment = UITextAlignmentRight;

Ah, ok, - i just accidentally hooked it up to the wrong button event. Ok, let's test the flow.

Ok - so far so good. Now, I want to make the "back" button disappear.

Good, it looks like I can do it in the "viewWillAppear" method:

- (void) viewWillAppear:(BOOL)animated{
self.navigationItem.hidesBackButton = YES;
}

Nothings ever easy - that caused the image to disappear! Do I have to call super or something?

Well, now it doesn't - but on the second go-round I get a bad access:


[self.navigationController popViewControllerAnimated:YES];

I believe I read somewhere that the "self.navigation" controller actually points to the navigation controller created in the app delegate, or the MainWindow.xlb to be precise. It's inherited through the view hierarchy, though I'd have to check that.

So, according SO, when a released object which isn't set to nil is sent a message, it will give you the bad access message.

I'm going to try this NSZombie trick from SO:

NSZombie will tell you what released object is being sent a message (aka EXC_BAD_ACCESS). This is a very useful tool when you get EXC_BAD_ACCESS, so learn how to use it.

To activate NSZombie do the following:

Get info of the executable.
Go to the arguments tab.
In the "Variables to be set in the environment:" section add:

Name: NSZombieEnabled Value: YES

Then run your app as usual and when it crashes it should tell you which deallocated object received the message.


Here's a better description of how to enable it:

http://www.cocoadev.com/index.pl?NSZombieEnabled

Use in Xcode:

Double-click an executable in the Executables group of your Xcode project.
Click the Arguments tab.
In the "Variables to be set in the environment:" section, make a variable called "NSZombieEnabled" and set its value to "YES".


Here's an even better one from SO:



In Xcode 4:

Press ⌥⌘R
From the tab "Info | Arguments | Diagnostics" select Diagnostics and click "Enable Zombie Objects".

From now on, released objects will turn into zombies and will appear in the debugger stack trace.

Pressing ⌥ ⌘R is the menu shortcut for selecting Product, keeping alt pressed, and clicking "Run...".
Clicking "Enable Zombie Objects" is the same as manually adding NSZombieEnabled YES in the section "Environment Variables" of the tab Arguments. Note that these Xcode settings are not used when you archive the application for App Store submission, so don't worry about submitting zombie infected apps.


Actually, I didn't see that option, so I added the environment variable as described above and shown below:





So, let's try it out.

Here's the message:

2011-09-22 17:51:28.182 JlptQuizApp[1971:207] *** -[UIImage isKindOfClass:]: message sent to deallocated instance 0x4e9ac20

Wow, that was really helpful. I had no idea. So, it's something to do with the image being displayed. Probably, something trying to send it a message once the pop happens?

Let's comment out the release and see what happens.

UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
//[img release]; // Release the image now that we have a UIImageView that contains it.
}



Well, now, instead if crashing, it's just freezing, or staying put, when I press the next button. Foolish thing. Why? What have I done to offend it?

Ah - there's an important thing going on here. It's showing the same image on the second time through. That means either it's not hitting the randomization code, which it should, or I'm not handling the image right. My guess is the 2nd but let's check.

No - I set a breakpoint on it, and it definitely didn't hit it.

Ok, so I think what I want to do is either use the view will appear instead of the view did load, or maybe just clean it up when it returns from that view. I'm going to play it conservative and just destroy the view. I wonder why the question view doesn't have that problem?

Let's just quickly check on the "viewWillAppear".

Oops - it's complicated. I'm supposed to read the UIViewController's guide - but I'm sleepy enough right now as it is.

The problem is, I don't really know what's causing the problem. But I do know that it's not going through the viewDidLoad. So, popping the controller off the stack isn't enough to get rid of it.

This post

http://forums.macrumors.com/archive/index.php/t-510787.html

Seems to indicate that the dealloc method should be called:


When you push a view controller, it is retained by the parent view. Likewise, when you pop the view controller, it is released by the parent. Therefore, popping the view controller will dealloc it if and only if its retain count is 0 after the pop.


So there must be something hanging on to it, otherwise it would be deallocated, right?

Ok, well, if I release the object after pushing it:

Well, I'm having a world of trouble with retain counts going below zero.

Ok, here's the key: the object has to be reallocated and initialized *each time* you call it. Then you push it onto the nav controller, and release it. The nav controller will retain it, and then when you pop it, the retain count will go to zero. This gets you the call to loadView that you need; and you aren't stuck with an old stale version.

Here's the code:



@interface StartController : UIViewController {

QuestionController *questionController;
SettingsController *settingsController;
NextLevelViewController *nextLevelViewController;
NextLevelController *nextLevelController;

}

// name the controller as a property

@property (nonatomic, retain) IBOutlet QuestionController *questionController;
@property (nonatomic, retain) IBOutlet SettingsController *settingsController;
@property (nonatomic, retain) IBOutlet NextLevelViewController *nextLevelViewController;


- (IBAction)openSettingsController
{
NSLog(@"openSettingsController");


settingsController = [[SettingsController alloc] init];

settingsController.appState = appState;

[self.navigationController pushViewController:settingsController animated:YES];

[settingsController release];
}



And in the settings controller, when the button to return / save gets pressed:


[self.navigationController popViewControllerAnimated:YES];


That's how you do it!

Wednesday, September 21, 2011

Making sure default prefs persist in iOS

Wait, I've suddenly lost my saving or retrieval of my settings. Let's take a look at the code.

Yeah, it's saving it:



[prefs setInteger:jlptLevel forKey:@"jlptLevel"];

[prefs setInteger:startNumInt forKey:@"startNum"];

[prefs setInteger:endNumInt forKey:@"endNum"];

// saving it all
[prefs synchronize];


But the question is, why isn't it persisting it between iterations of the iOS Simulator? I'm almost positive it was doing this before.

Maybe I should test it on my iTouch.

So this gives a way to check the path.

NSArray *path = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory, NSUserDomainMask, YES);
NSString *folder = [path objectAtIndex:0];
NSLog(@"Your NSUserDefaults are stored in this folder: %@/Preferences", folder)

But, why when I do synchronize doesn't it save it?

According to SO:

http://stackoverflow.com/questions/6033385/nsuserdefaults-not-being-saved-in-simulator



Your process is possibly terminated improperly so that NSUserDefaults do not have a chance to be stored. See also this and mostly this.

The suggestion in the second post I link to is to call synchronize in applicationDidEnterBackground:

Keep also in mind that terminating your app by stopping it in Xcode most often does not save user defaults.



Ok, well, there are a couple of problems. One is that the start num string is being dealt with in the on load, while the picker view is being dealt with the viewWillAppear. But, mostly - I'm not pulling the start an end number from prefs in either method. So, for now I'll just stick it where it's already mentioned.

Ok, here we go:

// Initialize start, end numbers

prefs = [NSUserDefaults standardUserDefaults];

startNumInt = [prefs integerForKey:@"startNum"];

if (startNumInt == 0){
startNumInt = 1;
}

NSString* startNumString = [NSString stringWithFormat:@"%d", startNumInt];

[startNum setText:startNumString];

startNumInt = [prefs integerForKey:@"endNum"];

if (endNumInt == 0){
endNumInt = 10;
}

NSString* endNumString = [NSString stringWithFormat:@"%d", endNumInt];

[endNum setText:endNumString];


So, all I had to do was add the retrieval. I must not have done it originally.

Hmm...that didn't work. Ok. Well, one problem is I was calling startInt endInt. So, I don't know if moving it to the viewWillAppear made a difference. Maybe not. But anyway, they're all in the same method now.



-(void) viewWillAppear: (BOOL) animated {


// retreive the stored level from prefs

prefs = [NSUserDefaults standardUserDefaults];

startNumInt = [prefs integerForKey:@"startNum"];


NSLog(@"startNum from prefs: %d ", startNumInt );

if (startNumInt == 0){
startNumInt = 1;
}

NSString* startNumString = [NSString stringWithFormat:@"%d", startNumInt];


[startNum setText:startNumString];

endNumInt = [prefs integerForKey:@"endNum"];

NSLog(@"endNum from prefs: %d ", endNumInt );

if (endNumInt == 0){
endNumInt = 10;
}

NSString* endNumString = [NSString stringWithFormat:@"%d", endNumInt];


[endNum setText:endNumString];


jlptLevel = [prefs integerForKey:@"jlptLevel"];


NSLog(@"jlptLevel from prefs: %d ", jlptLevel );



etc.

Getting the title right!



Ok, we've got our view display up and running. Let's keep going with that - we want to add a title, and a url to the artist's site.

We also want to mix in a bunch of the images. First. let's add and connect the variables.

Ok, for now I just hooked up the title. But now we want to just copy all the images into the resources folder.

Ok, they're copied. Now, the next step is to make a collections of the strings, an NSArray probably, that contains the name of those. The first thing to do is just to figure out how to create an NS array and then add the entries one by one.

Ok, from this url,

http://www.cocoadev.com/index.pl?NSMutableArray

We see this example:



NSMutableArray *array; //simply defines a mutable Array

array = [[NSMutableArray alloc] init]; //saves memory for the Array and initializes it.

arrayCount = [array count]; //counts all objects of this array and puts it into the arrayCount variable

[array addObject:[NSNumber numberWithInt:15]]; // appends "15" to the (mutable) array

arrayVariable = [array objectAtIndex:378]; //returns the 379th Object of the array and puts it into arrayVariable



So, now all we need to do is create the NSStrings to add. I think if we do it with a stringWithFormat, we don't need to worry about releasing. Here's an example.


int age = 25;
NSString *message;

message = [NSString stringWithFormat: @"Your age is %d", age];



Actually, we can add the string with creating a separate variable




NSMutableArray *array = [[NSMutableArray alloc] init];

[array addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[array addObject:[NSString stringWithFormat: @"fragile_as_snow"]];



And we can toss in the randomization while we're at it:



int index = arc4random() % [array count];


And pull it from the array and set the image to it. So, now we have a method like this

- (void)viewDidLoad
{

NSMutableArray *array = [[NSMutableArray alloc] init];

[array addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[array addObject:[NSString stringWithFormat: @"fragile_as_snow"]];

int index = arc4random() % [array count];

NSString *pictureName = [array objectAtIndex:index];


// NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:@"fragile_as_snow" ofType:@"png"];

NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:pictureName ofType:@"png"];


UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}

[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}


Ok, it seems to be showing the same picture. Let's add a couple of more for good measure. And also set the title in the label, which is something we forgot to do.

So, now we have this:


NSMutableArray *array = [[NSMutableArray alloc] init];

[array addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[array addObject:[NSString stringWithFormat: @"fragile_as_snow"]];
[array addObject:[NSString stringWithFormat: @"hanami_looking_at_cherries"]];
[array addObject:[NSString stringWithFormat: @"idle_hours"]];


int index = arc4random() % [array count];

NSString *pictureName = [array objectAtIndex:index];

art_title.text = pictureName;


Ok, but the text in the label is too small, and has the "...". Let's just increase the size of the label and see what happens.

Ok, that works, but I can't use the file name as the title - it has underlines in it. So, the simplest thing is to just create a parallel array. I actually should create a name value pair, and store that in the array. But I'm not going to right now - I'll make it a todo.

For now, let's use the logic from the android app, which has the correct titles:


R.drawable.all_was_darkness,
R.drawable.fragile_as_snow,
R.drawable.hanami_looking_at_cherries,
//R.drawable.idle_hours,
R.drawable.looking_at_the_moon,
R.drawable.sei_shonagan_and_the_hell_screen,
R.drawable.snow_cranes,
R.drawable.snow_viewing,
R.drawable.tasacode_whose_sleeve,
R.drawable.the_abbot,
R.drawable.the_go_game,
R.drawable.the_third_princess,
R.drawable.ukifune_and_karou_at_uji,
R.drawable.ukiune_wandering,
R.drawable.wisteria,
R.drawable.yugao };



Now, let's create an array with these titles:


imageTitleVector.add("All was Darkness");
imageTitleVector.add("Fragile as Snow");
imageTitleVector.add("Hanomi Looking at Cherries");
imageTitleVector.add("Looking at the Moon");
imageTitleVector.add("Sei Shonagan and the Hell Screen");
imageTitleVector.add("Snow Cranes");
imageTitleVector.add("Snow Viewing");
imageTitleVector.add("Tasacode Whose Sleeve");
imageTitleVector.add("The Abbot");
imageTitleVector.add("The Go Game");
imageTitleVector.add("The Third Princess");
imageTitleVector.add("Ukifune and Karou at Uji");
imageTitleVector.add("Ukufune Wandering");
imageTitleVector.add("Wisteria");
imageTitleVector.add("Yugao");

In fact, let's use the same ones for now and add more later, after the app is release.



- (void)viewDidLoad
{

NSMutableArray *fileNameArray = [[NSMutableArray alloc] init];

[fileNameArray addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[fileNameArray addObject:[NSString stringWithFormat: @"fragile_as_snow"]];
[fileNameArray addObject:[NSString stringWithFormat: @"hanami_looking_at_cherries"]];
[fileNameArray addObject:[NSString stringWithFormat: @"looking_at_the_moon"]];
[fileNameArray addObject:[NSString stringWithFormat: @"sei_shonagan_and_the_hell_screen"]];
[fileNameArray addObject:[NSString stringWithFormat: @"snow_cranes"]];
[fileNameArray addObject:[NSString stringWithFormat: @"snow_viewing"]];
[fileNameArray addObject:[NSString stringWithFormat: @"tasacode_whose_sleeve"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_abbot"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_go_game"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_third_princess"]];
[fileNameArray addObject:[NSString stringWithFormat: @"ukifune_and_karou_at_uji"]];
[fileNameArray addObject:[NSString stringWithFormat: @"ukiune_wandering"]];
[fileNameArray addObject:[NSString stringWithFormat: @"wisteria"]];
[fileNameArray addObject:[NSString stringWithFormat: @"yugao"]];


NSMutableArray *titleArray = [[NSMutableArray alloc] init];

[titleArray addObject:[NSString stringWithFormat: @"All was Darkness"]];
[titleArray addObject:[NSString stringWithFormat: @"Fragile as Snow"]];
[titleArray addObject:[NSString stringWithFormat: @"Hanami - Looking at Cherries"]];
[titleArray addObject:[NSString stringWithFormat: @"Looking at the Moon"]];
[titleArray addObject:[NSString stringWithFormat: @"Sei Shonagan and the Hell Screen"]];
[titleArray addObject:[NSString stringWithFormat: @"Snow Cranes"]];
[titleArray addObject:[NSString stringWithFormat: @"Snow Viewing"]];
[titleArray addObject:[NSString stringWithFormat: @"Tasacode Whose Sleeve"]];
[titleArray addObject:[NSString stringWithFormat: @"The Abbot"]];
[titleArray addObject:[NSString stringWithFormat: @"The Go Game"]];
[titleArray addObject:[NSString stringWithFormat: @"The Third Princess"]];
[titleArray addObject:[NSString stringWithFormat: @"Ukifune and Karou at Uji"]];
[titleArray addObject:[NSString stringWithFormat: @"Ukufune Wandering"]];
[titleArray addObject:[NSString stringWithFormat: @"Wisteria"]];
[titleArray addObject:[NSString stringWithFormat: @"Yugao"]];


int index = arc4random() % [fileNameArray count];

NSString *pictureName = [titleArray objectAtIndex:index];

art_title.text = pictureName;


// NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:@"fragile_as_snow" ofType:@"png"];

NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:pictureName ofType:@"png"];


UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}

[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}


Great. Let's try this out.

Ok. Well, it works, but there's a little problem with navigation. I want to hide the back button and add a button to continue to the next level.

Ok, added the button. Here's what the old "Answers" used for doing a similar type thing:



-(IBAction)popViewController:(id)sender;

// and

-(IBAction)popViewController:(id)sender {

// return to previous screen
[self.navigationController popViewControllerAnimated:YES];
}



Ok, let's hook it up to the button we just added to the nib.

Wow - this thing just did about 14 different things wrong:

1) Didn't show the image
2) The pop didn't work.
3) I had changed the JLPT level, and it started mixing in (I think) entries from the previous level 4 run
4) It crashed when I did something trivial

Let's track down #1 first.

Actually, it crashes when I hit "back" on the settings view - another pop the failing.

Darn it. I've list the images. What went wrong?

Ok, I've should decide if, umm, I should just go to the backup. These displays and views and the builder are so touchy.

Ok, I took the conservative route and went with the backup. I have my images back.

Let's conservatively just add four titles:

NSMutableArray *fileNameArray = [[NSMutableArray alloc] init];

[fileNameArray addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[fileNameArray addObject:[NSString stringWithFormat: @"fragile_as_snow"]];
[fileNameArray addObject:[NSString stringWithFormat: @"hanami_looking_at_cherries"]];
[fileNameArray addObject:[NSString stringWithFormat: @"idle_hours"]];


NSMutableArray *titleArray = [[NSMutableArray alloc] init];

[titleArray addObject:[NSString stringWithFormat: @"All was Darkness"]];
[titleArray addObject:[NSString stringWithFormat: @"Fragile as Snow"]];
[titleArray addObject:[NSString stringWithFormat: @"Hanami - Looking at Cherries"]];
[titleArray addObject:[NSString stringWithFormat: @"Looking at the Moon"]];


int index = arc4random() % [fileNameArray count];

NSString *pictureName = [titleArray objectAtIndex:index];

art_title.text = pictureName;



No! That kills the image display.

Wow - when I added a release for the two arrays, it worked! You've got to be careful about this releasing stuff in this language, otherwise it will mess you up - big time.

Ok, here's my code for now:


- (void)viewDidLoad
{

NSMutableArray *fileNameArray = [[NSMutableArray alloc] init];

[fileNameArray addObject:[NSString stringWithFormat: @"all_was_darkness"]];
[fileNameArray addObject:[NSString stringWithFormat: @"fragile_as_snow"]];
[fileNameArray addObject:[NSString stringWithFormat: @"hanami_looking_at_cherries"]];
[fileNameArray addObject:[NSString stringWithFormat: @"looking_at_the_moon"]];
[fileNameArray addObject:[NSString stringWithFormat: @"sei_shonagan_and_the_hell_screen"]];
[fileNameArray addObject:[NSString stringWithFormat: @"snow_cranes"]];
[fileNameArray addObject:[NSString stringWithFormat: @"snow_viewing"]];
[fileNameArray addObject:[NSString stringWithFormat: @"tasacode_whose_sleeve"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_abbot"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_go_game"]];
[fileNameArray addObject:[NSString stringWithFormat: @"the_third_princess"]];
[fileNameArray addObject:[NSString stringWithFormat: @"ukifune_and_karou_at_uji"]];
[fileNameArray addObject:[NSString stringWithFormat: @"ukiune_wandering"]];
[fileNameArray addObject:[NSString stringWithFormat: @"wisteria"]];
[fileNameArray addObject:[NSString stringWithFormat: @"yugao"]];



NSMutableArray *titleArray = [[NSMutableArray alloc] init];

[titleArray addObject:[NSString stringWithFormat: @"All was Darkness"]];
[titleArray addObject:[NSString stringWithFormat: @"Fragile as Snow"]];
[titleArray addObject:[NSString stringWithFormat: @"Hanami - Looking at Cherries"]];
[titleArray addObject:[NSString stringWithFormat: @"Looking at the Moon"]];
[titleArray addObject:[NSString stringWithFormat: @"Sei Shonagan and the Hell Screen"]];
[titleArray addObject:[NSString stringWithFormat: @"Snow Cranes"]];
[titleArray addObject:[NSString stringWithFormat: @"Snow Viewing"]];
[titleArray addObject:[NSString stringWithFormat: @"Tasacode Whose Sleeve"]];
[titleArray addObject:[NSString stringWithFormat: @"The Abbot"]];
[titleArray addObject:[NSString stringWithFormat: @"The Go Game"]];
[titleArray addObject:[NSString stringWithFormat: @"The Third Princess"]];
[titleArray addObject:[NSString stringWithFormat: @"Ukifune and Karou at Uji"]];
[titleArray addObject:[NSString stringWithFormat: @"Ukufune Wandering"]];
[titleArray addObject:[NSString stringWithFormat: @"Wisteria"]];
[titleArray addObject:[NSString stringWithFormat: @"Yugao"]];



int index = arc4random() % [fileNameArray count];

NSString *pictureName = [titleArray objectAtIndex:index];
// NSString *pictureName = [fileNameArray objectAtIndex:index];

art_title.text = pictureName;


// NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:@"fragile_as_snow" ofType:@"png"];

NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:pictureName ofType:@"png"];


UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}

[super viewDidLoad];

[fileNameArray release];
[titleArray release];

// Do any additional setup after loading the view from its nib.
}


Displaying a UIImage in a UIViewController




Ok, yesterday we managed to get the "NextLevel" view flow logic working. Now, we're going to see if we can't go ahead and add an image to it.

Ok, let's take a look at the class reference:

http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIImageView_Class/Reference/Reference.html

An image view object provides a view-based container for displaying either a single image or for animating a series of images.

// ok

For animating the images, the UIImageView class provides controls to set the duration and frequency of the animation. You can also start and stop the animation freely.

// don't need to worry about that right now

New image view objects are configured to disregard user events by default. If you want to handle events in a custom subclass of UIImageView, you must explicitly change the value of the userInteractionEnabled property to YES after initializing the object.

// don't need to worry about that either

When a UIImageView object displays one of its images, the actual behavior is based on the properties of the image and the view.


If either of the image’s leftCapWidth or topCapHeight properties are non-zero, then the image is stretched according to the values in those properties.

Otherwise, the image is scaled, sized to fit, or positioned in the image view according to the contentMode property of the view.

// what is the content mode property?

It is recommended (but not required) that you use images that are all the same size. If the images are different sizes, each will be adjusted to fit separately based on that mode.

All images associated with a UIImageView object should use the same scale.

If your application uses images with different scales, they may render incorrectly.


Ok, good intro, but I'd really just like to see some example code.

It looks like I'll be using this initWithImage method:

initWithImage:

Returns an image view initialized with the specified image.
- (id)initWithImage:(UIImage *)image
Parameters

image

The initial image to display in the image view.

Return Value

An initialized image view object.


I won't need this:

initWithImage:highlightedImage:

Returns an image view initialized with the specified regular and highlighted images.
- (id)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
Parameters

image

The initial image to display in the image view.
highlightedImage

The image to display if the image view is highlighted.


Ok, this thread looks like it will help out:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5573-cant-get-uiimageview-load-uiimage-runtime.html


Here's what the OP said worked for him:

- (void)viewDidLoad {



//taken from http://idevkit.com/forums/tutorials-code-samples-sdk/3-one-line-uiimage-url.html
UIImage *img = [[UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: @"http://someserver.com/somefile.jpg"]]] retain];
if (img != nil) { // Image was loaded successfully.
[imgView setImage:img];
[imgView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}
[super viewDidLoad];
}



Ok, we can try that for now, and switch to a file later. Let's fina a url with an image...wow, that was amazingly difficult - I finally just ftp'd into my own sight.

http://kanjisoft.com/appimages/correct_answer.png

So the above code becomes like this:



UIImage *img = [[UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: @"http://kanjisoft.com/appimages/correct_answer.png"]]] retain];
if (img != nil) { // Image was loaded successfully.
[imgView setImage:img];
[imgView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}
[super viewDidLoad];


Ok, so let's try this:


- (void)viewDidLoad
{

UIImage *img = [[UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: @"http://kanjisoft.com/appimages/correct_answer.png"]]] retain];
if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}

[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}


Good - we get the following image:




Ok, now let's figure out how to load it from a file.




UIImage *img = [[UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: @"http://kanjisoft.com/appimages/correct_answer.png"]]] retain];



Let's look up UIImage.

This method looks hopeful:

ImageWithContentsOfFile:

Creates and returns an image object by loading the image data from the file at the specified path.
+ (UIImage *)imageWithContentsOfFile:(NSString *)path
Parameters

path

The full or partial path to the file.

Return Value

A new image object for the specified file, or nil if the method could not initialize the image from the specified file.
Discussion

This method does not cache the image object.


This url offers a pretty good idea:

http://www.ericd.net/2009/07/iphone-previous-example-with-touches.html

UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image_1.jpg" ofType:nil]];


So, does this mean I just need to put an image in the resource folder?

Heres another example that separates the creation of the path from:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/34543-can-someone-please-show-me-how-do-imagewithcontentsoffile-method.html




NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:@"workpaper" ofType:@"png"];

imageView.image = [ UIImage imageWithContentsOfFile: imagePath];



So now we've culled it down to this "pathForResource". What is that, a parameter of NSBundle?

Let's look at the overview of NSBundle:



An NSBundle object represents a location in the file system that groups code and resources that can be used in a program.

NSBundle objects

locate program resources,

dynamically load and unload executable code,

and assist in localization.


You build a bundle in Xcode using one of these project types:

Application, Framework, plug-ins.

Although bundle structures vary depending on the target platform and the type of bundle you are building, the NSBundle class hides this underlying structure in most (but not all) cases.

Many of the methods you use to load resources from a bundle automatically locate the appropriate starting directory and look for resources in known places.

For information about application bundle structures (for Mac OS X and iOS), see Bundle Programming Guide.

// Bundle programming guide? Seriously?

For information about the structure of framework bundles, see Framework Programming Guide.

// Ugh.

For information about the structure of Mac OS X plug-ins, see Code Loading Programming Topics.

// Yeah, I'm all over it.

For additional information about how to load nib files and images in a Mac OS X application, see NSBundle Additions Reference.

// Good Christ.

For information about how to load nib files in an iOS application, see NSBundle UIKit Additions Reference.

Unlike some other Foundation classes with corresponding Core Foundation names (such as NSString and CFString), NSBundle objects cannot be cast (“toll-free bridged”) to CFBundle references.

// huh? toll-free bridged? What was wrong with "cast"?

If you need functionality provided in CFBundle, you can still create a CFBundle and use the CFBundle Reference API. See “Interchangeable Data Types” for more information on toll-free bridging.

// Sigh.



Ok, as far as I can tell it's a thing for identifying resources that has a lot of guides.

Let's just copy an image file into the resources group of the project from finder, and then try this:

#pragma mark - View lifecycle

- (void)viewDidLoad
{

NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:@"fragile_as_snow" ofType:@"png"];


UIImage *img = [ UIImage imageWithContentsOfFile: imagePath];

if (img != nil) { // Image was loaded successfully.
[imageView setImage:img];
[imageView setUserInteractionEnabled:NO];
[img release]; // Release the image now that we have a UIImageView that contains it.
}

[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}


Ok, it works. Heres an image:


Tuesday, September 20, 2011

Adding the "Congrats" view



Ok, now that we've figured out the percent correct and decided what to do when the answers aren't all correct, we've finally arrived at the point of figuring out what to do when the answers *are* all correct. And for that, we want to display a kind of "congratulations" view which shows a nice image of original art, and then pops them back to the start display.

So, it looks like we will build a quick view controller. That we can do with new, file and choose ui view controller subclass. We'll call it "NextLevelController".

Ok, move the xlb to resources, and then do what? Open up the xlb, then add a label saying "Congratulations - you have advanced to the next level!". Then, add a UIImageViewControl. Add a couple more labels underneath, one for the image title and one to the link for the url for the artist's site. And an ok button, or "Next Level" button.

Ok, here's the header:


#import


@interface NextLevelController : UIViewController {
UIImageView *image;
UILabel *art_title;
UILabel *url;

}

@property (nonatomic, retain) IBOutlet UIImageView *image;
@property (nonatomic, retain) IBOutlet UILabel *url;
@property (nonatomic, retain) IBOutlet UILabel *art_title;


@end


With corresponding @synthesize - I guess title is reserved or something, had to change to art_title.

Ok, making progress. Oh, yeah, let's create a procedure to call the finish.


- (IBAction)nextLevel
{
NSLog(@"NextLevelController, nextLevel called");
}


And a corresponding entry in the header. Ok, now, let's hook them up to the IB (this is always my favorite part).

Ok, let's run it and take a look.

Ah, actually, we haven't yet coded the pop logic. We can just throw this into the 100% correct logic:

QuizAppDelegate *delegate =
(QuizAppDelegate *)[[UIApplication sharedApplication] delegate];

[delegate.navController pushViewController:nextLevelController animated:YES];


The problem is I still need to have some kind of a variable. And in the StartController, the QuestionController and the SettingController were represented like so:

@interface StartController : UIViewController {

QuestionController *questionController;
SettingsController *settingsController;

}

// name the controller as a property

@property (nonatomic, retain) IBOutlet QuestionController *questionController;
@property (nonatomic, retain) IBOutlet SettingsController *settingsController;
@property (nonatomic, retain) AppState *appState;


And in the IB, they were represented in StartController.xlb as UIViews, I believe, and I was able to connect them.

It was under referencing outlets, which you can see by right clicking on the view, like so:




But, when I do the same in QuestionController.xlb, nothing shows up. I *hate* when stuff like this happens with ib.

Ah, I forgot - you have to set the class on the view controller. Phew. I hate this tricky IB stuff. I know, I just said I loved it. But, only when I don't get hung up it. It wasn't too bad this time.

Well, the victory is short-lived. It's crashing on a bad access now:

[delegate.navController pushViewController:nextLevelController animated:YES];



Either the problem is in the navController, or the nextLevelController.

Ok, maybe it's time I ventured off that track and tried a modal view. There's a good thread here:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/3643-pushviewcontroller-versus-presentmodalviewcontroller.html

that talks about it.

The nice thing about it is that he uses the initWithNibName, which I haven't used yet.

AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];


[self pushViewController:anotherViewController animated:YES];

versus

[self presentModalViewController:anotherViewController animated:YES];



Let's see if we can work though the initWithNibName:

Oh, I see, maybe, what the problem was - I'm obviously not *creating* the viewController. I guess. I though that attaching to to the view in the nib might do it. Am I creating it in start controller?

No, that's what I thought. Anyway, let's try the modal view.


NextLevelController *nextLevelController = [[NextLevelController alloc] initWithNibName:@"NextLevelController" bundle:nil];
//[self pushViewController:nextLevelController animated:YES];
[self presentModalViewController:nextLevelController animated:YES];



Nooooo - it shows up as a blank. The dreaded blank display.

Well, some searching on the net shows this:


UIView *modalView = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
modalView.opaque = NO;
modalView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5f];

UILabel *label = [[[UILabel alloc] init] autorelease];
label.text = @"Modal View";
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor blueColor];
label.opaque = YES;
[label sizeToFit];
[modalView addSubview:label];

[self.view addSubview:modalView];


So, this is building without the benefit of the IB. It actually does something, but not what I want - it's semi-transparent, no mater what I set opaque to. Anyway, I want to be able to create the view using IB, because it's so much easier.

So, the problem is, I don't really get the whole concept of the Nav View Controller. What is this thing? This thing here:

UINavigationController *navController;

It's defined in the App Delegate.

From the docs:

The UINavigationController class implements a specialized view controller that manages the navigation of hierarchical content. This class is not intended for subclassing. Instead, you use instances of it as-is in situations where you want your application’s user interface to reflect the hierarchical nature of your content. This navigation interface makes it possible to present your data efficiently and also makes it easier for the user to navigate that content.

The screens presented by a navigation interface typically mimic the hierarchical organization of your data. At each level of the hierarchy, you provide an appropriate screen (managed by a custom view controller) to display the content at that level. Figure 1 shows an example of the navigation interface presented by the Settings application in iOS Simulator. The first screen presents the user with the list of applications that contain preferences. Selecting an application reveals individual settings and groups of settings for that application. Selecting a group yields more settings and so on. For all but the root view, the navigation controller provides a back button to allow the user to move back up the hierarchy.


Ok - so it manages the navigation of hierarchical content - provides a back button for all but the root view. A specialized view controller.

More:


A navigation controller object manages the currently displayed screens using the navigation stack.

// so this is what provides the navigation stack.

At the bottom of this stack is the root view controller and at the top of the stack is the view controller currently being displayed.

// ok.

You use the methods of your navigation controller object to modify the stack at runtime.

// ok.

The most common operation is to push new view controllers onto the stack using the pushViewController:animated: method.

// right. That's exactly what I'm doing.

Pushing a new view controller object onto the stack causes the view of that view controller to be displayed and the navigation controls to be updated to reflect the change.

// not sure what navigation controls are - the back button?

You typically push view controllers in response to the user selecting an item that leads to the next level in your information hierarchy.

// Ok. I'm starting to get the picture.

In addition to pushing view controllers onto the navigation stack, you can also pop them using the popViewControllerAnimated: method.

// Go back from them. Release them to the ether, return to the previous view.

Although you can pop view controllers yourself, the navigation controller also provides a back button (when appropriate) that pops the top view controller in response to user interactions.

// Ok, this is all making sense.

A navigation controller object notifies its delegate object in response to changes in the active view controller.

// Hm? Ok, what is the delegate object in my case?

The delegate object is a custom object provided by your application that conforms to the UINavigationControllerDelegate protocol.

// Ok - let's check out the app delegate. No that just conforms to this:



You can use the methods of this protocol to respond to the change and perform additional setup or cleanup tasks.

For more information about how to integrate navigation controllers into your application, see View Controller Programming Guide for iOS.


Well, so, where is this delegate object?

Wait - I just had a *wicked good idea*. Why not pop the question view off the controller, and let the start controller handle it? That's cleaner, anyway.

Ah, here's the method I need:

/* call back method on pop of answer page */

- (void) viewWillAppear:(BOOL)animated{
// won't be animated on the first call


Ok, after a bit of fussing around, I ended up with this:


- (void) viewWillAppear:(BOOL)animated{

if (animated) {

if (appState.correctAnswers == [appState.shuffledQuestionDictKeyList count]){

QuizAppDelegate *delegate =
(QuizAppDelegate *)[[UIApplication sharedApplication] delegate];

[delegate.navController pushViewController:nextLevelViewController animated:YES];

}

}





And I just did a new nib with a new view controller - at it showed up: