31/07: Mapex Drum Masterclass
"Through a comprehensive series of interactive drum lessons, you will quickly become familiar with the basics of reading music notation while learning to play along in time to a click. Every lesson is demonstrated for you with essential tips and hints along the way so you'll know exactly what to do. The lessons have been purposely designed to make progressing through them as seamless and easy as possible. Before you know it, you will be reading drum music like a professional drummer!"
Read more at http://www.mapexdrummasterclass.com/ or go straight to the App Store here.
Abstractec Ltd. provided iPhone consultation and development services on behalf of Virtual Living Ltd. to produce the Mapex Drum Masterclass application. To find out more about our iPhone services, please contact us.
First up, let's take a little look at the method we need to use to achieve this. Under the UITableViewDelegate protocol there is the tableView:heightForRowAtIndexPath: method. The idea of this is to do as it says on the tin, return the height of the row at this index path.
So, how to work out this value. My preferred solution is to have a static method on the class that extends UITableRowCell. For this example, I will create a simple two-line implementation of a table row cell called TwoLineTableRowCell (for help on how to do this, please refer to the previous article). There will be two iVars in this class (in addition to the UILabel ivars): messageOne and messageTwo.
Our static method in this case will have the following signature:
+ (CGFloat) heightOfCell:(NSString *)messageOne messageTwo:(NSString *)messageTwo;
This method will be called within the tableView:heightForRowAtIndexPath: method in the UITableViewDelegate. In our example, we will have an array of example objects with a name and title properties, so the implementation will be
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
ExampleObject *example = [myExampleObjectArray objectAtIndex:indexPath.row];
return [TwoLineTableRowCell heightOfCell:example.name messageTwo:example.title];
}
Now, the implementation of our static method will be as follows:
+ (CGFloat) heightOfCell:(NSString *)messageOne messageTwo:(NSString *)messageTwo {
CGFloat lineOneHeight = [messageOne sizeWithFont:[UIFont boldSystemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap].height;
CGFloat lineTwoHeight = [messageOne sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap].height;
return lineOneHeight + lineTwoHeight + 28.0; // 28.0 is a little padding if required
}
Great, all done? Not quite. With this implementation, the cell will take up the correct height, but the UILabels will still be at the same location. The first thing to do is to update the #lines setting for each UILabel to 0. In addition to this, the prepareForReuse and layoutSubviews methods need updating.
The prepareForReuse method needs to reset the frames of the UILabel instances back to normal.
messageOneLabel.frame = CGRectMake(kLabelOneX, kLabelOneY, kLabelOneWidth, kLabelOneHeight);
messageTwoLabel.frame = CGRectMake(kLabelTwoX, kLabelTwoY, kLabelTwoWidth, kLabelTwoHeight);
Now, in the layoutSubviews method, you will do something along the lines of:
CGSize msgOneSize = [messageOne sizeWithFont:[UIFont boldSystemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap];
CGSize msgTwoSize = [messageTwo sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(280, 1000)
lineBreakMode:UILineBreakModeWordWrap];
CGFloat twoDiff = messageTwpLabel.frame.origin.x + messageOneLabel.frame.origin.x;
messageOneLabel.frame = CGRectMake(messageOneLabel.frame.origin.x, messageOneLabel.frame.origin.y,
messageOneLabel.frame.size.width, msgOneSize.height);
messageTwoLabel.frame = CGRectMake(msgOneSize.height + twoDiff, messageTwoLabel.frame.origin.y,
messageTwoLabel.frame.size.width, msgTwoSize.height);
Obviously you will have to set the content of the UILabels, but you should know how to do this by now!
I am going to use a view that will be a UIViewController that will adopt the UITableViewDelegate and UITableViewDatasource methods. First up create your view in the usual manner, add in the protocols and then add in the following attribute to the class:
IBOutlet UITableView *myTableView;
Obviously you can call your table view whatever you like. Now, in IB open up your NIB and navigate to the File's Owner. If you are using the latest version of Xcode, you will probably already have this set to your class. If not set this now. In your view, add in a Table View from the library. Set the dataSource and delegate of this view in the connections pane of the Inspector to your File's owner. Now, click on the File's Owner icon and display its connections. Connect 'myTableView' to the table view you have added to your view. Save.
At this point you can either add a UIImageView with an image and send this image to the back of the view hierarchy (under the Layout menu) or change the background colour of the view.
From here, go to the source of your class and add the following into the
- (void)viewDidLoadmethod:
[myTableView setBackgroundColor:[UIColor clearColor]];
When you display the view, your background image or colour should be visible under your grouped table.
25/03: UIImageView maximum width
Now currently this image is roughly 2800 pixels wide and works fine on the simulator. However when it is deployed onto the iPhone, no joy - it just has corruption on the screen from the image on the previous view. Once the image has been cropped to less than 2000 pixels (we're currently on 1944 pixels) then everything works fine.
This problem isn't anything to do with the animation routines because even when we just include the image statically with no animations, we still get the same corruption on the screen. Strange, but yet another case of a discrepancy between the Simulator and the actual device.
24/03: iPhone OS 3.0 thoughts
Push: push looks like being a winner. Although this is tinged with a slight degree of pessimism as it is no replacement for background processes. Apple's view that background processes will significantly hurt the battery life of the iPhone may be valid but I have a sneaking suspicion that this has more to do with the control Apple wishes to retain on the device. The acceptance testing of iPhone applications is not long enough to test whether or not I as a developer have not started a background process that, in a week, pops up an obscene message. With the push notification, everyone will be using Apple's servers which allows Apple to vet every message that is sent using this service. Also, there is no delivery guarantee with the SLA for your notifications.
Copy and Paste: about time. That's all one can say about that.
Accessory Support: not something that interests me directly as I have no means of manufacturing hardware but this presents a great opportunity to get the iPhone (and the iPod Touch specifically) into new arenas. Although I'm waiting for an external keyboard hardware manufacturers!
In application purchase: great - although not available in free applications. I think this is an oversight. The overriding strategy for marketing an iPhone application is to create a free application and use this to bring in paying customers (it worked for Quake!). Now, rather than doing this, wouldn't it be preferable to have one free application that the user can upgrade within the application? Rather than having to go back into the App Store and hunt for the game again, get bored and give up.
Peer to Peer: I'm intrigued by this. There are many non-game applications that would benefit from this, especially the 'in-game voice' features. Social networking, I'm thinking of you.
Maps: in Pay At The Pump and LPGFinder, we use a UIWebView that hits our server to serve back Google Maps pages. This new API allows this function to happen within the device making it quicker. Although I was unsure about the reference regarding having to provide your own tiles at the presentation from Apple. More investigation required
iPod Library Access: OK, I don't have any need for this so far. However I can see the use if you have an 'always on' app such as Palringo as you will now be able to listen to music while using IM
Audio Recording: called the AVFoundtion - 'V' for video? A hint about where this is going?
Core Data: I have to admit that this one got me excited. Being able to plumb a SQLite DB into a view with minimal coding. At last!
In Application Email: again, at last! Seriously, this is long, long overdue.
Streaming video: I have a couple of projects where this has been talked about with a 'wouldn't it be great if we could...' pretext. Well, now we can. At last.
Safari: Safari picks up a lot of the updates for streaming video and audio content. Also supported is the Geolocation JS classes.
Shared Keychain: share a keychain amongst the applications you write. This will allow, amongst other things, single sign on with all your applications.
Other enhancements around shaking the phone, global search, transition styles (including cross-fade, perfect for recreating Star Wars?), "Enhanced support in UIWebView for displaying previews of RTF, RTFD, PDF, iWork, and Office documents" (cool!), playback of multiple compressed files simultaneously
On the user front, MMS, stereo bluetooth and all the other features are great but have minimal bearing on development - although it would be nice to have had those a couple of years ago rather than playing catch-up with this release
What this release didn't include was a few bits that I was really hoping for: access to the calendar, background tasks (as mentioned above) and an improved simulator. However, the pros outweigh the cons from a developer stand point although it does still seem from a consumer point of view that 3.0 is more of a 'catch up to where we should have been with 1.0' release.
Connection Speed
One of the problems I have found is that the application can behave in a different way when running on EDGE or slow 3G connections. The main thing I have found is that the NSAutoReleasePool is flushed and objects are released when you are not expecting it. Being able to throttle the connection speed to the simulator would be a great way of checking this out. Being able to throttle the inbound and outbound bandwidth would allow the app to be forced to wait on the simulator just as it would in a real area of pool connectivity.
GPS Positions
I am quite tired of always being in Cupertino! Having the ability to set the GPS co-ordinates of the simulator in a similar manner to the way the Android simulator handles it would be very, very useful. By having this you could test the performance of the app as it takes a journey from A to B and ensure that everything works correctly
Create your UITableViewCell
@interface MyTableViewCell : UITableViewCell {
IBOutlet UILabel *exampleLabel;
}
And add a layoutSubviews method within the implementation section; ie -
- (void)layoutSubviews {
exampleLabel.text = @"This is my example!";
/*
You may also have a little more control over your font here also
*/
exampleLabel.font = [UIFont boldSystemFontOfSize:12.0];
}
Now, create a new empty XIB file and call it whatever you want - I will use MyTableViewCell to match the Obj-C class. Open this XIB up using Interface Builder and look at the two items in the doc window: File's Owner and First Responder. In the inspector, change the File's Owner class to be UIViewController if this isn't already set. From the library, drag a Table View Cell into the doc window after the First Responder. Change the class of this new Table View Cell to be MyTableViewCell. Now associate the main view with the Table View Cell by clicking on File Owner and then on the Connections Inspector on the entry for 'view', drag a line from the empty circle at the end of this line onto the 'My Table View Cell' entry in the doc window.
OK, now let's set up the label. Double click on the 'My Table View Cell' entry in the doc window. Drag a label from the library into the view of 'My Table View Cell' that should have been opened. Within the outlets group in the connections inspector for the table view cell, drag a line from the circle at the end of the 'exampleLabel' line onto the label we just added to the table cell. Save the view.
That's all great, but we haven't yet told our code how to load up the XIB for this table cell view yet. We need to have a view controller conforming to the UITableViewDelegate and UITableViewDataSource protocols (which we won't go into here). Within the '- (UITableViewCell *)tableView:(UITableView *)TableView cellForRowAtIndexPath:(NSIndexPath *)indexPath' method, we need to create our UITableViewCell implementation.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyTableViewCell";
MyTableViewCell *cell =
(MyTableViewCell *)[inTableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
UIViewController *c = [[UIViewController alloc]
initWithNibName:@"MyTableViewCell" bundle:nil];
cell = (MyTableViewCell *)c.view;
[c release];
// code here to set whatever values in MyTableViewCell
}
return cell;
}
Obviously this is a pretty basic example, but the code follows the same principle for all other elements you may wish to add to your UITableViewCell.
17/11: Icon Rounding and Reflection
01/11: Self-Signed Certificates
The method I am going to describe allows you to accept the self-signed certificate without adding a 'pem' file to the keychain and uses the CoreServices Framework.
First up, we have to create a CFHTTPMessageRef reference using the CFHTTPMessageCreateRequest from CoreServices.
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(
kCFAllocatorDefault,
CFSTR("POST"),
(CFURLRef) [NSURL URLWithString:@"https://--your site url--/"],
kCFHTTPVersion1_1);
Next up, we need to grab the stream for this request of type CFReadStreamRef.
CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
Now, with for this stream, we need to tell the iPhone to not bother if presented with a self-signed certificate.
CFMutableDictionaryRef securityDictRef = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (securityDictRef != nil) {
CFDictionarySetValue(securityDictRef, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
CFReadStreamSetProperty(stream, kCFStreamPropertySLLSettings, securityDictRef);
CFRelease(securityDictRef);
}
And there we go. Essentially we're telling the CFReadStreamRef we do not want the certificate chain validated. We also have the option of using kCFStreamSSLAllowsExpiredCertificates for allowing expired certificates through, kCFStreamSSLAllowsExpiredRoots to do the same for expired roots and KCFStreamSSLCertificates to add in a certificate of your own. For more details, check out the "CFStream Socket Additions" documentation.


