style sheet problem - three20

I wrote code like:
+ (CGFloat)tableView:(UITableView*)tableView rowHeightForObject:(id)item {
CustomTTTableSubtitleItem* captionedItem = item;
CGFloat maxWidth = tableView.width - kHPadding*2;
CGSize titleSize = [captionedItem.title sizeWithFont:TTSTYLEVAR(myTitleFont) constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
}
Got this exception:
2011-07-24 03:10:18.762 xinyou[15941:b303] -[TTDefaultStyleSheet
myTitleFont]: unrecognized selector sent to instance 0x5b5e120
2011-07-24 03:10:18.765 xinyou[15941:b303] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[TTDefaultStyleSheet myTitleFont]: unrecognized selector sent to
instance 0x5b5e120'
* Call stack at first throw: ( 0 CoreFoundation
0x0119a5a9 exceptionPreprocess + 185 1 libobjc.A.dylib
0x012ee313 objc_exception_throw + 44 2 CoreFoundation
0x0119c0bb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187 3
CoreFoundation 0x0110b966 __forwarding + 966
4 CoreFoundation 0x0110b522
_CF_forwarding_prep_0 + 50 5 xinyou
0x000081f9 +[CustomTTTableSubtitleItemCell
tableView:rowHeightForObject:] + 186 6 xinyou
0x000a6c92 -[TTTableViewVarHeightDelegate
tableView:heightForRowAtIndexPath:] + 156 7 UIKit
0x0064a6d5 -[UISectionRowData
In this exception you can see [TTDefaultStyleSheet myTitleFont]: unrecognized selector sent to instance 0x5b5e120 but actually myTitleFont defined in XYDefaultStyleSheet and I've imported XYDefaultStyleSheet.h in my class. XYDefaultStyleSheet.h and XYDefaultStyleSheet.m are like:
XYDefaultStyleSheet.h
#import "Three20/Three20.h"
#interface XYDefaultStyleSheet : TTDefaultStyleSheet
#property(nonatomic,readonly) UIColor* myHeadingColor;
#property(nonatomic,readonly) UIColor* mySubtextColor;
#property(nonatomic,readonly) UIColor* myTitleColor;
#property(nonatomic,readonly) UIFont* myTitleFont;
#property(nonatomic,readonly) UIFont* myHeadingFont;
#property(nonatomic,readonly) UIFont* mySubtextFont;
#end
XYDefaultStyleSheet.m
#import "XYDefaultStyleSheet.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
#implementation XYDefaultStyleSheet
///////////////////////////////////////////////////////////////////////////////////////////////////
// styles
///////////////////////////////////////////////////////////////////////////////////////////////////
// public colors
- (UIColor*)myTitleColor {
return [UIColor blackColor];
}
- (UIColor*)myHeadingColor {
return RGBCOLOR(80, 110, 140);
}
- (UIColor*)mySubtextColor {
return [UIColor grayColor];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// public fonts
- (UIFont*)myTitleFont {
return [UIFont boldSystemFontOfSize:16];
}
- (UIFont*)myHeadingFont {
return [UIFont boldSystemFontOfSize:13];
}
- (UIFont*)mySubtextFont {
return [UIFont systemFontOfSize:12];
}
#end
why always tell [TTDefaultStyleSheet myTitleFont] ... if the problem is really myTitleFont, it should be [XYDefaultStyleSheet myTitleFont], why TTDefaultStyleSheet?

got it! Init my style sheet in AppDelegate.

This is an answer to #Jason Zhao's answer about initialising within the AppDelegate.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
[TTStyleSheet setGlobalStyleSheet:[[[CustomDefaultStyleSheet alloc]
init] autorelease]];
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Original code is from here, which has a lot of useful information about using TTStyleSheet's:
Three20 Stylesheets iPhone Tutorial

Related

xcode unrecognized selector sent to instance error

I am new to xcode iOS programming and I really need your help with the error of unrecognised selector sent to instance. Below is my implementation
//.h file
#interface AppAccountController : UIViewController <UIAlertViewDelegate>{
IBOutlet UITextField *txtName;
IBOutlet UITextField *txtEmail;
IBOutlet UIDatePicker *dateOB;
IBOutlet UITextField *txtPwd;
IBOutlet UITextField *txtRePwd;
}
- (IBAction)btnSave:(id)sender;
#end
//.m file
#interface AppAccountController ()
#property (strong, nonatomic) IBOutlet UITextField *txtName;
#property (strong, nonatomic) IBOutlet UITextField *txtEmail;
#property (strong, nonatomic) IBOutlet UIDatePicker *dateOB;
#property (strong, nonatomic) IBOutlet UITextField *txtPwd;
#property (strong, nonatomic) IBOutlet UITextField *txtRePwd;
#property (strong, nonatomic) IBOutlet UIButton *btnSave;
#end
#implementation AppAccountController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)btnSave:(id)sender {
// Create strings to store the text info
NSString *name = [txtName text];
NSString *email = [txtEmail text];
NSDate *dob = [dateOB date];
NSString *passwd = [txtPwd text];
NSString *repasswd = [txtRePwd text];
if ([txtName.text isEqualToString:#""])
{
[txtName becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Name Field is empty. Please enter a name"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 1;
}
else if ([txtEmail.text isEqualToString:#""])
{
[txtEmail becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Email Field is empty. Please enter an Email"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 2;
}
else if ([txtPwd.text isEqualToString:#""])
{
[txtPwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Password Field is empty. Please enter a Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 3;
}
else if ([txtRePwd.text isEqualToString:#""])
{
[txtRePwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Confirm Password Field is empty. Please enter to confirm Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 4;
}
else if (![passwd isEqualToString:repasswd])
{
[txtPwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Passwords do not match. Please enter to confirm Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 5;
}
else{
// Store the data
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:name forKey:#"name"];
[defaults setObject:email forKey:#"email"];
[defaults setObject:passwd forKey:#"passwd"];
[defaults setObject:dob forKey:#"dob"];
[defaults synchronize];
NSLog(#"Data saved");
[self performSegueWithIdentifier:#"saveLoad" sender:self];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if(alertView.tag == 1) {
[txtName becomeFirstResponder];
NSLog(#"Name AlertView is clicked");
}
else if(alertView.tag == 2) {
[txtEmail becomeFirstResponder];
NSLog(#"Email AlertView is clicked");
}
else if(alertView.tag == 3) {
[txtPwd becomeFirstResponder];
NSLog(#"Password AlertView is clicked");
}
else if(alertView.tag == 4) {
[txtRePwd becomeFirstResponder];
NSLog(#"Re-Password AlertView is clicked");
}
else if(alertView.tag == 5) {
[txtRePwd becomeFirstResponder];
NSLog(#"Re-Password AlertView is clicked");
}
}
#end
Whenever I press the btnSave button or after moving on to another UI element (one element gaining focus after another one losses it), I get unrecognised selector sent to instance.
UPDATE
2014-12-18 18:06:23.794 XPB[964:35743] -[AppAccountController btnSave:forEvent:]: unrecognized selector sent to instance 0x7fd949d923f0
2014-12-18 18:06:23.834 XPB[964:35743] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AppAccountController btnSave:forEvent:]: unrecognized selector sent to instance 0x7fd949d923f0'
*** First throw call stack:
(
0 CoreFoundation 0x000000010bd2df35 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010a182bb7 objc_exception_throw + 45
2 CoreFoundation 0x000000010bd3504d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x000000010bc8d27c ___forwarding___ + 988
4 CoreFoundation 0x000000010bc8ce18 _CF_forwarding_prep_0 + 120
5 UIKit 0x00000001087e98be -[UIApplication sendAction:to:from:forEvent:] + 75
6 UIKit 0x00000001088f0410 -[UIControl _sendActionsForEvents:withEvent:] + 467
7 UIKit 0x00000001088ef7df -[UIControl touchesEnded:withEvent:] + 522
8 UIKit 0x000000010882f308 -[UIWindow _sendTouchesForEvent:] + 735
9 UIKit 0x000000010882fc33 -[UIWindow sendEvent:] + 683
10 UIKit 0x00000001087fc9b1 -[UIApplication sendEvent:] + 246
11 UIKit 0x0000000108809a7d _UIApplicationHandleEventFromQueueEvent + 17370
12 UIKit 0x00000001087e5103 _UIApplicationHandleEventQueue + 1961
13 CoreFoundation 0x000000010bc63551 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
14 CoreFoundation 0x000000010bc5941d __CFRunLoopDoSources0 + 269
15 CoreFoundation 0x000000010bc58a54 __CFRunLoopRun + 868
16 CoreFoundation 0x000000010bc58486 CFRunLoopRunSpecific + 470
17 GraphicsServices 0x000000010c64e9f0 GSEventRunModal + 161
18 UIKit 0x00000001087e8420 UIApplicationMain + 1282
19 XPB 0x00000001083965b3 main + 115
20 libdyld.dylib 0x000000010ce4b145 start + 1
21 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
The problem is that btnSave: and btnSave:forEvent: are two different things.
Your AppAccountController has a btnSave: method, so it can be sent the btnSave: message. But it has no btnSave:forEvent: method. Unfortunately, that's the selector you specified to be used whenever the button is pressed. The button is trying to send btnSave:forEvent: to your AppAccountController, and AppAccountController doesn't know what to do with that. So it crashes.
The easiest solution is to fix the button so that it sends btnSave: and not btnSave:forEvent:. (You will almost never need the two-parameter variant of an action method in any case.)

NSManagedObject subclass updating custom property - getting KVO errors

EDIT: figured out that the NSNumber was a bad type to bind to. Using NSString instead solved the problem
I have a ToDo entity which has a to-one relationship ("title") with the ToDoTitle entity, and the relationship in reverse("todos") is to-many . i.e. a todo can have one title, but a title can have multiple todos.
Now I want to add a separate NSNumber on top of my NSManagedObject for count of the "incomplete todos" i.e. they have a todo.todoStatus == FALSE.
It looks like this:
#interface ToDoTitle : NSManagedObject
#property (nonatomic, strong) NSNumber * displayOrder;
#property (nonatomic, strong) NSString * title;
#property (nonatomic, strong) NSSet *todo; // this is saved in Core Data model
#property (nonatomic, strong) NSNumber *incompleteCount; // this isn't in the Core Data model
I can tweak the incompleteCount getter to return the correct count of items, and this works:
- (NSNumber *) incompleteCount {
[self willAccessValueForKey:#"incompleteCount"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"todoStatus == FALSE"];
NSSet *incomplete = [[self primitiveValueForKey:#"todo"] filteredSetUsingPredicate: predicate];
[self didAccessValueForKey:#"incompleteCount"];
return #([incomplete count]);
}
Now what I really use this for is for Cocoa Bindings, to display this number on a button. The binding works from the NSButton to a NSTableViewCell with the model key path = "objectValue.managedObject.incompleteCount.stringValue"
This also works fine, and displays the correct data initially. I also implement this in TodoTitle to keep track of changes to the 'todo' set:
+ (NSSet*) keyPathsForValuesAffectingIncompleteCount {
return [[NSSet alloc] initWithObjects: #"todo", nil];
}
so now when I add or delete ToDo objects, the count for each title gets updated correctly as well.
What I can't figure out how to update this incompleteCount when I update one of the todo object's todoStatus. I need to do something like this in the ToDo managed object:
- (void) setTodoStatus:(NSNumber *)todoStatus {
[self willChangeValueForKey:#"todoStatus"];
[self setPrimitiveValue:todoStatus forKey:#"todoStatus"];
if (todoStatus.boolValue == YES) {
self.todotitle.incompleteCount = #(self.todotitle.incompleteCount.integerValue - 1);
}
[self didChangeValueForKey:#"todoStatus"];
}
I get this error:
2014-08-03 22:27:26.632 CJ[9487:303] Cannot update for observer
for the key path
"incompleteCount.stringValue" from , most
likely because the value for the key "incompleteCount" has changed
without an appropriate KVO notification being sent. Check the
KVO-compliance of the ToDoTitle class.
with this stack trace:
2014-08-03 22:27:26.634 Contacts Journal[9487:303] (
0 CoreFoundation 0x00007fff8b5c525c __exceptionPreprocess + 172
1 libobjc.A.dylib 0x00007fff8f25be75 objc_exception_throw + 43
2 CoreFoundation 0x00007fff8b5c510c +[NSException raise:format:] + 204
3 Foundation 0x00007fff92c134b0 -[NSKeyValueNestedProperty object:withObservance:didChangeValueForKeyOrKeys:recurse:forwardingValues:] + 1003
4 Foundation 0x00007fff92ba6fd9 NSKeyValueDidChange + 166
5 Foundation 0x00007fff92babbe6 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 118
6 CoreData 0x00007fff88542b01 -[NSManagedObject didChangeValueForKey:] + 113
7 CJ 0x00000001000d664e -[ToDo setTodoStatus:] + 318
8 CJ 0x00000001000ff2a9 -[ToDo(ToDo_Category) updateStatusToComplete] + 345
I'm just not sure to update the incompleteTodos object in a KVO-compliant way. I've tried various things, like wrapping the call inside willChangeValueForKey: and didChangeValueForKey: but doesn't seem to help:
- (void) setTodoStatus:(NSNumber *)todoStatus {
[self willChangeValueForKey:#"todoStatus"];
[self setPrimitiveValue:todoStatus forKey:#"todoStatus"];
if (todoStatus.boolValue == YES) {
[self.todotitle willChangeValueForKey:#"incompleteCount"];
self.todotitle.incompleteCount = #(self.todotitle.incompleteCount.integerValue - 1);
[self.todotitle didChangeValueForKey:#"incompleteCount"];
}
[self didChangeValueForKey:#"todoStatus"];
}
I'm not sure what I'm doing wrong here. I've also tried adding a custom setter in TodoTitle, with and without the willChange/didChange calls:
- (void) setIncompleteCount:(NSNumber *)incompleteCount_ {
incompleteCount = incompleteCount_;
}
but still get the same problem.
Any idea what's wrong here?
Well, it turns out that if I change incompleteCount type to an NSString instead of an NSNumber, and turn the integer into a string, it works fine! Must be some problem with NSNumber's stringValue not updating with the binding? I don't know. Wasted half a day on this!

how to fix uipageviewcontroller exc_bad_access if page turn is not completed?

I'm getting an exc_bad_access in my app and I can't figure out how to fix it.
I just upgraded to Xcode 4.5 and I'm targeting IOS 5.0. This is also my first time using UIPageViewController. I prefer to use storyboard as much as possible.
What I'm trying to do is recreate a golf course flip book that contains an image on which you can scroll and zoom. I've basically got a mashup now of several tutorials that is mostly working;
1) I have setup a UIPageviewController, which loads a UIScrollview, which adds a imageView to it.
2) flipping by gesture or tap is working, scrolling is working, I have pinch zoom working and a custom single and two finger tapping working for zoom in / out.
3) The crash appears when you start to flip the page with a sliding gesture, but then release your finger. This basically cancels the flip but then a msg gets send to a zombied object.
Here is my 'GuideViewController.h', it acts as the datasource as well as the root.
#import <UIKit/UIKit.h>
#import "YardageHoleViewController.h"
#interface GuideViewController : UIViewController <UIPageViewControllerDataSource>
#property (strong, nonatomic) UIPageViewController *pageController;
#property (strong, nonatomic) NSArray *pageContent;
- (YardageHoleViewController *)viewControllerAtIndex:(NSUInteger)index storyboard (UIStoryboard *)storyboard;
- (NSUInteger)indexOfViewController:(YardageHoleViewController *)viewController;
#end
And here is the Implementation
#import "GuideViewController.h"
#import "GolfCourseAppDelegate.h"
#import "Hole.h"
#interface GuideViewController ()
#end
#implementation GuideViewController
#synthesize pageContent = _pageContent;
#synthesize pageController = _pageController;
- (void)viewWillDisappear:(BOOL)animated
{
[[[GolfCourseAppDelegate sharedDelegate] locationManager] stopUpdatingLocation];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[[GolfCourseAppDelegate sharedDelegate] locationManager] startUpdatingLocation];
[self createContentPages];
NSDictionary *options =
[NSDictionary dictionaryWithObject:
[NSNumber numberWithInteger:UIPageViewControllerSpineLocationMin]
forKey: UIPageViewControllerOptionSpineLocationKey];
self.pageController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationVertical options: options];
//self.pageController.delegate = self;
self.pageController.dataSource = self;
[[self.pageController view] setFrame:[[self view] bounds]];
YardageHoleViewController *initialViewController = [self viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
[self addChildViewController:self.pageController];
[[self view] addSubview:[self.pageController view]];
[self.pageController didMoveToParentViewController:self];
}
- (YardageHoleViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
NSLog(#"getting data view controller at index: %d", index);
// Return the data view controller for the given index.
if (([self.pageContent count] == 0) || (index >= [self.pageContent count])) {
return nil;
}
// Create a new view controller and pass suitable data.
YardageHoleViewController *yardageHoleViewController = [storyboard instantiateViewControllerWithIdentifier:#"YardageHoleViewController"];
yardageHoleViewController.dataObject = [self.pageContent objectAtIndex:index];
return yardageHoleViewController;
}
- (NSUInteger)indexOfViewController:(YardageHoleViewController *)viewController
{
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
NSLog(#"returning indexOfViewController : %d", [self.pageContent indexOfObject:viewController.dataObject]);
return [self.pageContent indexOfObject:viewController.dataObject];
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSLog(#"getting view controller before view controller");
NSUInteger index = [self indexOfViewController:(YardageHoleViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSLog(#"getting view controller After view controller");
NSUInteger index = [self indexOfViewController:(YardageHoleViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageContent count]) {
return nil;
}
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
- (void) createContentPages
{
NSLog(#"creating content Pages");
int totalHoles = [[[GolfCourseAppDelegate appData] objectForKey:#"holes"] count];
NSMutableArray *holeData = [[NSMutableArray alloc] init];
for (int i = 1; i < totalHoles+1; i++)
{
Hole *newHole = [[Hole alloc] initWithHoleNumber:i imageUrl:[NSString stringWithFormat:#"hole%#%d.jpg", (i < 10) ? #"0" : #"", i]];
NSLog(#"Hole image url:%#",newHole.imageUrl);
//int holeNumber = i;
//NSString *imageUrl = [NSString stringWithFormat:#"hole%#%d.jpg", (i < 10) ? #"0" : #"", i];
[holeData addObject:newHole];
}
self.pageContent = [[NSArray alloc] initWithArray:holeData];
NSLog(#"count of holeData %d", self.pageContent.count);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Now for the view we are flipping through 'YardageHoleViewController.h'
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <CoreLocation/CoreLocation.h>
#interface YardageHoleViewController : UIViewController <UIScrollViewDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (assign, nonatomic) int hole;
#property (assign, nonatomic) int totalHoles;
#property (strong, nonatomic) id dataObject;
#property (strong, nonatomic) IBOutlet UILabel *frontLabel;
#property (strong, nonatomic) IBOutlet UILabel *middleLabel;
#property (strong, nonatomic) IBOutlet UILabel *backLabel;
- (IBAction)nextPage:(id)sender;
- (IBAction)previousPage:(id)sender;
- (IBAction)infoPage:(id)sender;
- (IBAction)homePage:(id)sender;
- (void)updateDistanceDisplay;
- (NSString *)formatDistance:(NSNumber *)distance;
#end
Here you can see I've got a few things going on. There is some sub views to show distance to cup based on location etc. You also see some outlets, I wanted to have buttons at the top to navigate in addition to the gestures, right now that's not working because the gestures are overriding the button taps (another question for later).
So here is the meat and potatoes 'YardageHoleViewController.m'
#import "YardageHoleViewController.h"
#import "GolfCourseAppDelegate.h"
#import "Hole.h"
#interface YardageHoleViewController ()
#property (nonatomic, strong) UIImageView *imageView;
- (void)centerScrollViewContents;
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer;
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer;
#end
#implementation YardageHoleViewController
#synthesize scrollView = _scrollView;
#synthesize hole = _hole;
#synthesize totalHoles = _totalHoles;
#synthesize imageView = _imageView;
#synthesize frontLabel = _frontLabel;
#synthesize middleLabel = _middleLabel;
#synthesize backLabel = _backLabel;
#synthesize dataObject = _dataObject;
/* The point of this method is to get around a slight annoyance with UIScrollView, which is: if the scroll view content size is smaller than its bounds, then it sits at the top-left rather than in the center. This method positions the image view such that it is always in the center of the scroll view’s bounds.
*/
- (void)centerScrollViewContents {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
CGPoint pointInView = [recognizer locationInView:self.imageView];
CGFloat newZoomScale = self.scrollView.zoomScale * 1.5f;
newZoomScale = MIN(newZoomScale, self.scrollView.maximumZoomScale);
CGSize scrollViewSize = self.scrollView.bounds.size;
CGFloat w = scrollViewSize.width / newZoomScale;
CGFloat h = scrollViewSize.height / newZoomScale;
CGFloat x = pointInView.x - (w / 2.0f);
CGFloat y = pointInView.y - (h / 2.0f);
CGRect rectToZoomTo = CGRectMake(x, y, w, h);
[self.scrollView zoomToRect:rectToZoomTo animated:YES];
}
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer {
// Zoom out slightly, capping at the minimum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f;
newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale);
[self.scrollView setZoomScale:newZoomScale animated:YES];
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
// Return the view that you want to zoom
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
// The scroll view has zoomed, so you need to re-center the contents
[self centerScrollViewContents];
}
- (void)viewDidLoad {
[super viewDidLoad];
Hole *hole = (Hole*)self.dataObject;
self.hole = hole.holeNumber;
UIImage *image = [UIImage imageNamed:hole.imageUrl];
self.imageView = [[UIImageView alloc] initWithImage:image];
self.imageView.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size};
[self.scrollView addSubview:self.imageView];
self.scrollView.contentSize = image.size;
//Here you’re setting up two gesture recognizers: one for the double-tap to zoom in, and one for the two-finger-tap to zoom out.
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:doubleTapRecognizer];
UITapGestureRecognizer *twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewTwoFingerTapped:)];
twoFingerTapRecognizer.numberOfTapsRequired = 1;
twoFingerTapRecognizer.numberOfTouchesRequired = 2;
[self.scrollView addGestureRecognizer:twoFingerTapRecognizer];
[[[GolfCourseAppDelegate sharedDelegate] locationManager] setDelegate:self];
[self updateDistanceDisplay];
self.totalHoles = [[[GolfCourseAppDelegate appData] objectForKey:#"holes"] count];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat scaleWidth = scrollViewFrame.size.width / self.scrollView.contentSize.width;
self.scrollView.minimumZoomScale = scaleWidth;
self.scrollView.maximumZoomScale = 1.5f;
self.scrollView.zoomScale = scaleWidth;
[self centerScrollViewContents];
}
- (void) didRotate:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight) {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackTranslucent];
NSString *moviePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"hole%#%d", (self.hole < 10) ? #"0" : #"", self.hole] ofType:#"mp4"];
MPMoviePlayerViewController *viewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:moviePath]];
viewController.moviePlayer.controlStyle = MPMovieControlStyleNone;
viewController.view.backgroundColor = [UIColor blackColor];
[self presentMoviePlayerViewControllerAnimated:viewController];
} else {
[self dismissMoviePlayerViewControllerAnimated];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
[self updateDistanceDisplay];
}
- (void) updateDistanceDisplay {
CLLocation *userLocation = [[GolfCourseAppDelegate sharedDelegate] userLocation];
if (userLocation != nil) {
NSMutableDictionary *holeLocations = [[[GolfCourseAppDelegate appData] objectForKey:#"holes"] objectForKey:[NSString stringWithFormat:#"hole%d", self.hole]];
if (round([[[holeLocations objectForKey:#"front"] objectForKey:#"lat"] floatValue]) == 0) {
self.frontLabel.text = #"---";
} else {
CLLocation *frontLocation = [[CLLocation alloc] initWithLatitude:[[[holeLocations objectForKey:#"front"] objectForKey:#"lat"] floatValue] longitude:[[[holeLocations objectForKey:#"front"] objectForKey:#"lng"] floatValue]];
if (([frontLocation distanceFromLocation:userLocation]/1000)>1000){
self.frontLabel.text = #"Out of Range";
}else{
self.frontLabel.text = [self formatDistance:[NSNumber numberWithFloat:([frontLocation distanceFromLocation:userLocation]/1000)]];
}
}
if (round([[[holeLocations objectForKey:#"middle"] objectForKey:#"lat"] floatValue]) == 0) {
self.middleLabel.text = #"---";
} else {
CLLocation *middleLocation = [[CLLocation alloc] initWithLatitude:[[[holeLocations objectForKey:#"middle"] objectForKey:#"lat"] floatValue] longitude:[[[holeLocations objectForKey:#"middle"] objectForKey:#"lng"] floatValue]];
self.middleLabel.text = [self formatDistance:[NSNumber numberWithFloat:([middleLocation distanceFromLocation:userLocation]/1000)]];
}
if (round([[[holeLocations objectForKey:#"back"] objectForKey:#"lat"] floatValue]) == 0) {
self.backLabel.text = #"---";
} else {
CLLocation *backLocation = [[CLLocation alloc] initWithLatitude:[[[holeLocations objectForKey:#"back"] objectForKey:#"lat"] floatValue] longitude:[[[holeLocations objectForKey:#"back"] objectForKey:#"lng"] floatValue]];
self.backLabel.text = [self formatDistance:[NSNumber numberWithFloat:([backLocation distanceFromLocation:userLocation]/1000)]];
}
}
}
- (NSString *) formatDistance:(NSNumber *)distance {
NSNumber *displayDistance;
NSString *unitSuffix = #"";
// Convert km to yards if prefs say so.
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
if ([[preferences stringForKey:#"measurementUnit"] isEqualToString:#"meters"]) {
distance = [NSNumber numberWithFloat:([distance floatValue]*1000.0)];
if ([distance floatValue] < 1000.0) {
displayDistance = distance;
unitSuffix = #"";
} else {
displayDistance = [NSNumber numberWithFloat:([distance floatValue]/1000.0)];
unitSuffix = #"km";
}
} else {
distance = [NSNumber numberWithFloat:([distance floatValue]*1.0936133*1000.0)];
if ([distance floatValue] < 1760.0) {
displayDistance = distance;
unitSuffix = #"";
} else {
displayDistance = [NSNumber numberWithFloat:([distance floatValue]/1760.0)];
unitSuffix = #"mi";
}
}
NSNumberFormatter *decimalStyle = [[NSNumberFormatter alloc] init];
[decimalStyle setFormatterBehavior:NSNumberFormatterBehavior10_4];
[decimalStyle setNumberStyle:NSNumberFormatterDecimalStyle];
[decimalStyle setRoundingMode:NSNumberFormatterRoundFloor];
[decimalStyle setRoundingIncrement:[NSNumber numberWithFloat:1.0]];
NSString *finalDistance = [decimalStyle stringFromNumber:displayDistance];
return [NSString stringWithFormat:#"%#%#", finalDistance, unitSuffix];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[self setImageView:nil];
[self setScrollView:nil];
[self setFrontLabel:nil];
[self setBackLabel:nil];
[self setBackLabel:nil];
[self setFrontLabel:nil];
[self setMiddleLabel:nil];
[super viewDidUnload];
}
- (IBAction)nextPage:(id)sender {
//TODO
// [((UIPageViewController*)self.parentViewController) setViewControllers:
// target direction:UIPageViewControllerNavigationForward completion:nil];
}
- (IBAction)previousPage:(id)sender {
//TODO
// [((UIPageViewController*)self.parentViewController) setViewControllers:<#(NSArray *)#> direction:UIPageViewControllerNavigationDirectionReverse animated:true completion:nil];
}
- (IBAction)infoPage:(id)sender {
//TODO
}
- (IBAction)homePage:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
#end
Whew! lots of reading. So what I tried first was to set the exception break point. No luck. Then I added lots of NSlog statements to see where we crash, and then finally looked for zombies in instruments. Here we see on the malloc "An Objective-C message was sent to a deallocated object (zombie) at address: 0x1386e0e0" in the YardageHoleViewController.
From my NSLog statements I can see that a successful page turn looks like this;
2012-12-16 13:33:52.280 BAP Template[1365:13a03] getting data view controller at index: 0
//started flip right here >
2012-12-16 13:34:06.289 BAP Template[1365:13a03] getting view controller After view controller
2012-12-16 13:34:06.290 BAP Template[1365:13a03] returning indexOfViewController : 0
2012-12-16 13:34:06.292 BAP Template[1365:13a03] getting data view controller at index: 1
and here is what happens when you start to flip then release
2012-12-16 13:36:18.613 BAP Template[1365:13a03] getting data view controller at index: 0
//started flip then released
2012-12-16 13:36:21.828 BAP Template[1365:13a03] getting view controller After view controller
2012-12-16 13:36:21.829 BAP Template[1365:13a03] returning indexOfViewController : 0
2012-12-16 13:36:21.831 BAP Template[1365:13a03] getting data view controller at index: 1
So in some sense it is trying to act like it completed the flip, but we didn't, and then that's when we have a bad time =(
I've set everything to strong, and I really don't know what to try next?
Any suggestions on my code in general would really be appreciated. Thanks in advance!
UPDATE I looked at the crash log in organizer
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libobjc.A.dylib 0x3737bf78 objc_msgSend + 16
1 CoreLocation 0x3405ddc0 -[CLLocationManager onClientEventLocation:] + 1136
2 CoreLocation 0x3405d77e -[CLLocationManager onClientEvent:supportInfo:] + 194
3 CoreLocation 0x34057e38 __CLClientInvokeCallback_block_invoke_0 + 48
I've since commented out the two lines that start/stop updating location in viewDidLoad / willDisappear in guideViewController.
No more crashing, but why?
Since location updating is started when the view loads, the method
- (void)locationManager:didUpdateToLocation:fromLocation:
gets called continuously in the simulator, like every second, however on the device it only gets called when it detects movement. Inside that method was the call [self updateDistanceDisplay] and that is why it was crashing. The fix I've implemented works well on the device now but it is not bulletproof.
First, in the appDelegate, we want to fix the locationManager.distanceFilter so that not every minor movement triggers the delegate method.
self.locationManager.distanceFilter = 1.0f;
Next, a modification to the didUpdateToLocation method to only update the display if there was a change in the latitude or longitude.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if ((newLocation.coordinate.latitude!=oldLocation.coordinate.latitude)||(newLocation.coordinate.longitude!=oldLocation.coordinate.longitude))
[self updateDistanceDisplay];
}
Again, not bulletproof. There is a case where say user is traveling in a golf cart fast enough and trying to flip pages, we could get into the crash state.

Custom UIFont rendering causes app to crash iOS 6

I test an app that uses a custom UIFont. This font is used in a UILabel that is zoomable -- it has a CATiledLayer layer.
This is the code for the UILabel class:
#import "ZoomableLabel.h"
#implementation ZoomableLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setupView];
}
return self;
}
//- (id)initWithCoder:(NSCoder *)aDecoder {
// self = [super initWithCoder:aDecoder];
// if (self) {
// // Initialization code
// [self setupView];
// }
// return self;
//}
-(void)awakeFromNib {
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 3;
layerForView.levelsOfDetail = 1;
}
-(void)setText:(NSString *)value {
self.layer.contents = nil;
[super setText:value];
[self setNeedsDisplay];
}
-(void)setTextColor:(UIColor *)value {
self.layer.contents = nil;
[super setTextColor:value];
[self setNeedsDisplay];
}
#end
When I run the app on a device or the simulator for the first time (that is the app is installed for the first time on the device or the simulator) I get a crash. Then this crash never happens again! UPDATE: The crash happens VERY randomly (especially when demoing the app...) but not only the first time. This is all the info I managed to get from XCode:
Thread 3 name: Dispatch queue: com.apple.root.default-priority
Thread 3 Crashed: 0 WebCore 0x333adbfa WTF::HashTable<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> >::rehash(int) + 42
1 WebCore 0x333adcd4 WTF::HashTableAddResult<WTF::HashTableIterator<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> > > WTF::HashTable<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> >::add<WTF::HashMapTranslator<WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*> >, WebCore::FontData const*, WebCore::GlyphPageTreeNode*>(WebCore::FontData const* const&, WebCore::GlyphPageTreeNode* const&) + 56
2 WebCore 0x333a5cac WebCore::GlyphPageTreeNode::getChild(WebCore::FontData const*, unsigned int) + 264
3 WebCore 0x333a55d8 WebCore::Font::glyphDataAndPageForCharacter(int, bool, WebCore::FontDataVariant) const + 528
4 WebCore 0x333a53b6 WebCore::Font::glyphDataForCharacter(int, bool, WebCore::FontDataVariant) const + 18
5 WebCore 0x333a4b36 WebCore::WidthIterator::advance(int, WebCore::GlyphBuffer*) + 398
6 WebCore 0x333a4794 WebCore::Font::floatWidthForSimpleText(WebCore::TextRun const&, WebCore::GlyphBuffer*, WTF::HashSet<WebCore::SimpleFontData const*, WTF::PtrHash<WebCore::SimpleFontData const*>, WTF::HashTraits<WebCore::SimpleFontData const*> >*, WebCore::GlyphOverflow*) const + 60
7 WebCore 0x333a4546 WebCore::Font::width(WebCore::TextRun const&, WTF::HashSet<WebCore::SimpleFontData const*, WTF::PtrHash<WebCore::SimpleFontData const*>, WTF::HashTraits<WebCore::SimpleFontData const*> >*, WebCore::GlyphOverflow*) const + 250
8 WebCore 0x333a60e0 WebCore::truncateString(WTF::String const&, float, WebCore::Font const&, unsigned int (*)(WTF::String const&, unsigned int, unsigned int, unsigned short*, bool), bool, float*, bool, float, bool) + 296
9 WebCore 0x333a5fac WebCore::StringTruncator::rightTruncate(WTF::String const&, float, WebCore::Font const&, WebCore::StringTruncator::EnableRoundingHacksOrNot, float&, bool, float) + 60
10 WebKit 0x375fc718 applyEllipsisStyle(WTF::String const&, WebEllipsisStyle, float, WebCore::Font const&, WebCore::StringTruncator::EnableRoundingHacksOrNot, float*, bool, float, bool) + 464
11 WebKit 0x375ff3a8 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:renderedStringOut:drawUnderline:] + 5036
12 WebKit 0x375fdfe8 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:renderedStringOut:] + 112
13 WebKit 0x375fdf64 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:] + 108
14 WebKit 0x375fdee4 -[NSString(WebStringDrawing) _web_drawInRect:withFont:ellipsis:alignment:lineSpacing:includeEmoji:truncationRect:measureOnly:] + 108
15 WebKit 0x375fde64 -[NSString(WebStringDrawing) _web_sizeInRect:withFont:ellipsis:lineSpacing:] + 80
16 UIKit 0x353698c2 -[NSString(UIStringDrawing) sizeWithFont:constrainedToSize:lineBreakMode:lineSpacing:] + 122
17 UIKit 0x3535daa6 -[UILabel _legacy_drawTextInRect:baselineCalculationOnly:] + 594
18 UIKit 0x35321c5a -[UILabel _drawTextInRect:baselineCalculationOnly:] + 162
19 UIKit 0x35320a26 -[UILabel drawTextInRect:] + 446
20 UIKit 0x35320860 -[UILabel drawRect:] + 68
21 UIKit 0x3531fd20 -[UIView(CALayerDelegate) drawLayer:inContext:] + 360
22 QuartzCore 0x37b84bb8 -[CALayer drawInContext:] + 108
23 QuartzCore 0x37c62624 tiled_layer_render(_CAImageProvider*, unsigned int, unsigned int, unsigned int, unsigned int, void*) + 1416
24 QuartzCore 0x37bd755c CAImageProviderThread(unsigned int*, bool) + 508
25 libdispatch.dylib 0x37b5e95c _dispatch_root_queue_drain + 248
26 libdispatch.dylib 0x37b5eabc _dispatch_worker_thread2 + 80
27 libsystem_c.dylib 0x38862a0e _pthread_wqthread + 358
28 libsystem_c.dylib 0x388628a0 start_wqthread + 4
Does anybody has any idea on this? I think this crash does not happen on an iOS 5 device...
Here is my take on Summon's answer which uses Core Text to address issues with languages other than English (although I have only tested Swedish) and odd font symbols. Note that it uses initWithCoder as I'm using StoryBoard. It also shows how to center the text in the label horizontally (had less luck with vertical where I simply had to test my way to the right placement).
Header:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface DNLabel : UILabel
#end
Implementation:
#import "DNLabel.h"
#import <CoreText/CoreText.h>
#implementation DNLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setupView];
}
return self;
}
-(void)awakeFromNib
{
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 10;
layerForView.levelsOfDetail = 10;
}
// LEAVE IT EMPTY
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
// These calls inside this method are thread SAFE
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// Core Text version
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName,
self.font.pointSize,
NULL);
// set color
CGColorRef color = [[[UIColor blackColor] colorWithAlphaComponent:0.75f] CGColor];
// pack it into attributes dictionary
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(id)CFBridgingRelease(ctFont), (id)kCTFontAttributeName,
color, (id)kCTForegroundColorAttributeName,
nil, (id)kCTUnderlineStyleAttributeName, nil];
// make the attributed string
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:self.text
attributes:attributesDict];
// flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// check size of text and set position centered horizontally
CGSize size = [self.text sizeWithFont:self.font];
CGContextSetTextPosition(context, self.bounds.size.width/2 - size.width/2, 1);
// draw
CTLineRef line = CTLineCreateWithAttributedString(
(CFAttributedStringRef)CFBridgingRetain(stringToDraw));
CTLineDraw(line, context);
}
#end
These are the .h and .m classes of a zoomable label based on CATiledLayer that never crashes:
Header:
#import <QuartzCore/QuartzCore.h>
#interface ZoomableLabel : UILabel {
CGPoint textDrawPoint;
CGPoint shadowDrawPoint;
}
#end
Implementation:
#import "ZoomableLabel.h"
#implementation ZoomableLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setupView];
}
return self;
}
-(void)awakeFromNib {
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 3;
layerForView.levelsOfDetail = 1;
textDrawPoint = CGPointMake(11, -22);
shadowDrawPoint = CGPointMake(10, -23);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
textDrawPoint = CGPointMake(11, -42);
shadowDrawPoint = CGPointMake(8, -46);
}
}
// DO NOT USER THESE METHODS ANYMORE
//-(void)setText:(NSString *)value {
//// self.layer.contents = nil;
// [super setText:value];
// [self setNeedsDisplayInRect:self.bounds];
//}
//
//-(void)setTextColor:(UIColor *)value {
//// self.layer.contents = nil;
// [super setTextColor:value];
// [self setNeedsDisplayInRect:self.bounds];
//}
// LEAVE IT EMPTY
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
// These calls inside this method are thread SAFE
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// Do all your drawing here. Do not use UIGraphics to do any drawing, use Core Graphics instead.
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextSelectFont(context, [self.font.fontName UTF8String], self.font.pointSize, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
// CGContextSetShouldAntialias(context, true);
CGContextSetFillColorWithColor(context, [[[UIColor blackColor] colorWithAlphaComponent:0.75f] CGColor]);
CGContextShowTextAtPoint(context, shadowDrawPoint.x, shadowDrawPoint.y, [self.text UTF8String], self.text.length);
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
CGContextShowTextAtPoint(context, textDrawPoint.x, textDrawPoint.y, [self.text UTF8String], self.text.length);
}
#end

Terminating app due to uncaught exception 'NSInvalidArgumentException'

I have the following problem. I inherited an app and I am trying to modify it. The original functionality lets me add a task with some text notes. I want to add an icon to make it more visually appealing. When I modify the code signature to pass the icon (in reality I am only passing the name of the icon in a NSString) the modules stops working and the app throws the following error, basically an unrecognized selector:
2011-03-19 22:41:17.713 app[82653:207] -[RemindersDataManager addReminder:notes:locations:icon:]: unrecognized selector sent to instance 0xc700bc0
2011-03-19 22:41:17.716 app[82653:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RemindersDataManager addReminder:notes:locations:icon:]: unrecognized selector sent to instance 0xc700bc0'
*** Call stack at first throw:
(
0 CoreFoundation 0x0162abe9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x0177f5c2 objc_exception_throw + 47
2 CoreFoundation 0x0162c6fb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x0159c366 ___forwarding___ + 966
4 CoreFoundation 0x0159bf22 _CF_forwarding_prep_0 + 50
5 app 0x000158b1 -[GeoRemindersManager addReminder:notes:locations:icon:] + 121
6 app 0x0000f268 -[AddReminderViewController addReminder] + 655
7 UIKit 0x0053ea6e -[UIApplication sendAction:to:from:forEvent:] + 119
8 UIKit 0x005cd1b5 -[UIControl sendAction:to:forEvent:] + 67
9 UIKit 0x005cf647 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 527
10 UIKit 0x005ce1f4 -[UIControl touchesEnded:withEvent:] + 458
11 UIKit 0x007c9987 _UIGestureRecognizerSortAndSendDelayedTouches + 3609
12 UIKit 0x007ca0fc _UIGestureRecognizerUpdateObserver + 927
13 CoreFoundation 0x0160bfbb __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 27
14 CoreFoundation 0x015a10e7 __CFRunLoopDoObservers + 295
15 CoreFoundation 0x01569bd7 __CFRunLoopRun + 1575
16 CoreFoundation 0x01569240 CFRunLoopRunSpecific + 208
17 CoreFoundation 0x01569161 CFRunLoopRunInMode + 97
18 GraphicsServices 0x01ce4268 GSEventRunModal + 217
19 GraphicsServices 0x01ce432d GSEventRun + 115
20 UIKit 0x0054d42e UIApplicationMain + 1160
21 app 0x00002d1c main + 102
22 app 0x00002cad start + 53
)
terminate called after throwing an instance of 'NSException'.
These are the original module's signature
AddReminderViewController.h
// user actions
- (IBAction)addReminder;
AddReminderViewController.m
- (IBAction)addReminder {
if ([self dataVerification]) {
BOOL result = NO;
// try to add one geo fence or several geo fences
if (self.address) {
result = [[GeoRemindersManager sharedInstance] addReminder:self.titleField.text notes:self.detailsField.text
coordinate:self.coordinate address:self.address];
}
else if (self.locations) {
result = [[GeoRemindersManager sharedInstance] addReminder:self.titleField.text notes:self.detailsField.text locations:self.locations];
}
// successfully added - close
if (result) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
GeoRemindersManager.h
// shared instance
+ (GeoRemindersManager *)sharedInstance;
// workflow
- (void)start;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes
coordinate:(CLLocationCoordinate2D)coordinate address:(NSString *)address;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes location:(LocationEntity *)location;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes locations:(NSArray *)locations;
- (void)changeReminder:(NSString *)title title:(NSString *)newTitle notes:(NSString *)newNotes;
- (void)removeReminder:(NSString *)title;
- (ReminderEntity *)getReminderWithTitle:(NSString *)title;
- (NSArray *)getLoctionsWithCoordinate:(CLLocationCoordinate2D)coordinate;
- (void)wakeByLocalNotification:(NSDictionary *)info;
- (void)memoryWarning;
- (CLLocation *)getCurrentLocation;
// extra
- (BOOL)checkReminderExistance:(NSString *)title;
- (void)updateDelegatesWithReminder:(NSString *)title notes:(NSString *)notes;
GeoRemindersManager.m
+ (GeoRemindersManager *)sharedInstance {
geoapp_iphoneAppDelegate *appDelegate = (geoapp_iphoneAppDelegate *)[MLSApplication instance];
return appDelegate.remindersManager;
}
#pragma mark -
#pragma mark workflow
- (void)start {
// start first locating
[deviceLocation forceStart];
// add geo fences to device location
NSArray *locations = [geoDataManager getAllRemindersLocations];
[deviceLocation addMonitoringLocations:locations];
// invoke delegate
[delegateMap didAddMonitoringLocations:locations];
}
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes
coordinate:(CLLocationCoordinate2D)coordinate address:(NSString *)address
{
if (![self checkReminderExistance:title]) {
return NO;
}
// all verifications are OK, adding reminder
[geoDataManager addReminder:title notes:notes coordinate:coordinate address:address];
[self updateDelegatesWithReminder:title notes:notes];
return YES;
}
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes location:(LocationEntity *)location {
return [self addReminder:title notes:notes locations:[NSArray arrayWithObject:location]];
}
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes locations:(NSArray *)locations {
if (![self checkReminderExistance:title]) {
return NO;
}
// all verifications are OK, adding reminder
[geoDataManager addReminder:title notes:notes locations:locations];
[self updateDelegatesWithReminder:title notes:notes];
return YES;
}
and the modified ones:
AddReminderViewController.m
- (IBAction)addReminder {
if ([self dataVerification]) {
BOOL result = NO;
// try to add one geo fence or several geo fences
if (self.address) {
result = [[GeoRemindersManager sharedInstance] addReminder:self.titleField.text notes:self.detailsField.text coordinate:self.coordinate address:self.address icon:(NSString *)iconButton.currentTitle];
}
else if (self.locations) {
result = [[GeoRemindersManager sharedInstance] addReminder:self.titleField.text notes:self.detailsField.text locations:self.locations icon:(NSString *)iconButton.currentTitle];
}
// successfully added - close
if (result) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
}
GeoRemindersManager.h
// shared instance
+ (GeoRemindersManager *)sharedInstance;
// workflow
- (void)start;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes
coordinate:(CLLocationCoordinate2D)coordinate address:(NSString *)address icon:(NSString *)icon;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes location:(LocationEntity *)location icon:(NSString *)icon;
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes locations:(NSArray *)locations icon:(NSString *)icon;
- (void)changeReminder:(NSString *)title title:(NSString *)newTitle notes:(NSString *)newNotes icon:(NSString *)icon;
- (void)removeReminder:(NSString *)title;
- (ReminderEntity *)getReminderWithTitle:(NSString *)title;
- (NSArray *)getLoctionsWithCoordinate:(CLLocationCoordinate2D)coordinate;
- (void)wakeByLocalNotification:(NSDictionary *)info;
- (void)memoryWarning;
- (CLLocation *)getCurrentLocation;
// extra
- (BOOL)checkReminderExistance:(NSString *)title;
- (void)updateDelegatesWithReminder:(NSString *)title notes:(NSString *)notes icon:(NSString *)icon;
GeoRemindersManager.m
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes
coordinate:(CLLocationCoordinate2D)coordinate address:(NSString *)address icon:(NSString *)icon
{
if (![self checkReminderExistance:title]) {
return NO;
}
// all verifications are OK, adding reminder
[geoDataManager addReminder:title notes:notes coordinate:coordinate address:address icon:icon];
[self updateDelegatesWithReminder:title notes:notes icon:icon];
return YES;
}
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes location:(LocationEntity *)location icon:(NSString *)icon{
return [self addReminder:title notes:notes locations:[NSArray arrayWithObject:location icon:icon]];
}
- (BOOL)addReminder:(NSString *)title notes:(NSString *)notes locations:(NSArray *)locations icon:(NSString *)icon {
if (![self checkReminderExistance:title]) {
return NO;
}
// all verifications are OK, adding reminder
[geoDataManager addReminder:title notes:notes locations:locations icon:icon];
[self updateDelegatesWithReminder:title notes:notes];
return YES;
}
So in short, the only thing I added was an additional argument to those function for the icon name as a NSString… Any clues? All help greatly appreciated! Thanks
The error message indicates that the unrecognized selector was called on an object of class RemindersDataManager, probably from the line
[geoDataManager addReminder:title notes:notes locations:locations icon:icon];
You should find the implementation of that class and add your icon parameter to its methods too (or stop passing icon to it if it has no use for the icon).

Resources