how to fix NSCollectionViewItem overlap problem? - macos

i use NSCollectionView to display a horizontal list. but all of the cells overlap in the first cell position, like this.
i don't have enough reputation to post images. [cell1,cell2,cell3], they all overlap in the first cell position. so it look like just a cell, but actually it has 3 cells.
my main code:
ViewController.h
- (void)viewDidLoad {
[super viewDidLoad];
self.flowLayout.scrollDirection = NSCollectionViewScrollDirectionHorizontal;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
self.collectionView.selectable = YES;
self.collectionView.allowsMultipleSelection = YES;
[self.collectionView registerNib:[[NSNib alloc] initWithNibNamed:#"ScreenShotCellView" bundle:nil] forItemWithIdentifier:NSStringFromClass([ScreenShotCellView class])];
}
#pragma mark - NSCollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
[self.collectionView.collectionViewLayout invalidateLayout];
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.screenshotItems.count;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSCollectionViewItem *itemCell = [self.collectionView makeItemWithIdentifier:NSStringFromClass([ScreenShotCellView class]) forIndexPath:indexPath];
if ([itemCell isKindOfClass:[ScreenShotCellView class]]) {
ScreenShotCellView *cellView = (ScreenShotCellView *)itemCell;
ScreenshotItem *itemData = [self.screenshotItems objectAtIndex:indexPath.item];
cellView.item = itemData;
}
return itemCell;
}
#pragma mark - NSCollectionViewDelegateFlowLayout
- (NSSize)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(100, self.collectionView.frame.size.height - 20);
}
- (CGFloat)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
- (CGFloat)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
How can i do something to fix it?

I have found the reason.
The problem is in my custom cell.
#interface ScreenShotCellView ()
#property (weak) IBOutlet NSView *containerView;
#end
#implementation ScreenShotCellView
- (void)loadView {
self.view = self.containerView;
}
#end
in the loadView method. self.view = self.containerView; make always display in the first cell.
after changed like this, it becomes normal.
- (void)loadView {
self.view = [[NSView alloc] init];
[self.view addSubview:self.containerView];
}

Related

NSOutlineView show indentation marker

How do I generate indentation marker for NSOutlineView?
I am not sure if this is an inbuilt functionality because it appears in other apps like Instruments
Update
I tried solving the problem by iterating all the children of the item that the row represents and show the marker on all children rows based on indentation level, but I faced a few problems
How to handle the case where the item has thousands of children. One simply cannot draw marker to every row as NSOutlineView would draw rows as they are displayed
When I scroll the NSOutlineView, the mouse moves out of the specified row but mouseExited is not being called. Thus the user has to manually move the mouse to reload the highlighting.
I had solved this problem but my solution looks hacky hence wanted to know if there is a better solution. And hence the question
First to receive mouseEntered: and mouseExited: events you need to setup a tracking rect using NSTrackingArea.
I would start with a subclass of NSTableRowView thats overwrites setFrame: making sure the tracking rect gets updated when the view is resize:
#interface TableRowView : NSTableRowView {
NSBox *_box;
NSTrackingArea *_trackingArea;
}
#property (weak) id owner;
#property (copy) NSDictionary<id, id> *userInfo;
#property (nonatomic) CGFloat indentation;
#property (nonatomic) BOOL indentationMarkerHidden;
#end
#implementation TableRowView
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
if (_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow owner:[self owner] userInfo:[self userInfo]];
[self addTrackingArea:_trackingArea];
}
#end
To use the NSTableRowView subclass, implement the NSOutlineViewDelegate messages like this:
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
{
TableRowView *view = [[TableRowView alloc] init];
view.owner = self;
view.userInfo = item;
return view;
}
With this in place you're ready to receive mouseEntered: and mouseExited: events. Use NSOutlineView levelForItem: together with indentationPerLevel to calculate the position of the marker NSBox.:
- (void)mouseEntered:(NSEvent *)event
{
id item = [event userData];
CGFloat indentation = [_outlineView levelForItem:item] * [_outlineView indentationPerLevel];
[self setIndentationMarker:indentation hidden:NO item:item];
}
- (void)mouseExited:(NSEvent *)event
{
id item = [event userData];
[self setIndentationMarker:0.0 hidden:YES item:item];
}
Now you get the NSTableRowView subclass by rowViewAtRow:makeIfNecessary: and recursively do the same for all children in your data:
- (void)setIndentationMarker:(CGFloat)indentation hidden:(BOOL)hidden item:(NSDictionary *)item
{
TableRowView *view = [_outlineView rowViewAtRow:[_outlineView rowForItem:item] makeIfNecessary:NO];
view.indentationMarkerHidden = hidden;
view.indentation = indentation;
for (NSMutableDictionary *child in [item objectForKey:#"children"]) {
[self setIndentationMarker:indentation hidden:hidden item:child];
}
}
Now layout the NSBox the NSTableRowView subclass:
#implementation TableRowView
- (instancetype)init
{
self = [super init];
if (self) {
_indentationMarkerHidden = YES;
_box = [[NSBox alloc] init];
_box.boxType = NSBoxCustom;
_box.borderWidth = 0.0;
_box.fillColor = [NSColor tertiaryLabelColor];
_box.hidden = _indentationMarkerHidden;
[self addSubview:_box];
}
return self;
}
- (void)layout
{
[super layout];
NSRect rect = [self bounds];
rect.origin.x = _indentation + 7;
rect.size.width = 10;
_box.frame = rect;
}
- (void)setIndentation:(CGFloat)indentation
{
_indentation = indentation;
[self setNeedsLayout:YES];
}
- (void)setIndentationMarkerHidden:(BOOL)indentationMarkerHidden
{
if (_indentationMarkerHidden != indentationMarkerHidden) {
_indentationMarkerHidden = indentationMarkerHidden;
_box.hidden = indentationMarkerHidden;
}
}
#end
This enough to make a basic version like here:

Perform segue with accessoryButton

I am doing master + detail pages. I have successfully made tapping the cell rows in the master page jump to detail page, using the code below
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"MasterToDetail" sender:indexPath];
}
However becuase my master page cell rows also have accessory buttons, i need to make these buttons able to jump to the detail page too through tapping. So i implemented code
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"MasterToDetail" sender:self];
}
Howver the details dispalyed is not correct. It always shows the first detail data in the datalist. I changed sender:self to sender:indexPath also no working.
I used stackoverflow answers from here and here. I am a beginner, so some concepts still no clear sorry.
Edited
- preparesegue for masterviewcontroller.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DetailViewController *detailController =segue.destinationViewController;
SearchDataDetail *bug = [self.bugs objectAtIndex:self.tableView.indexPathForSelectedRow.row];
detailController.detailItem = bug;
}
-detailviewcontroller.m
#import "DetailViewController.h"
#import "SearchData.h"
#import "SearchDataDetail.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
- (void)configureView
{
// Update the user interface for the detail item.
self.rateView.notSelectedImage = [UIImage imageNamed:#"shockedface2_empty.png"];
self.rateView.halfSelectedImage = [UIImage imageNamed:#"shockedface2_half.png"];
self.rateView.fullSelectedImage = [UIImage imageNamed:#"shockedface2_full.png"];
self.rateView.editable = YES;
self.rateView.maxRating = 5;
self.rateView.delegate = self;
if (self.detailItem) {
self.titleField.text = self.detailItem.data.title;
self.rateView.rating = self.detailItem.data.rating;
self.imageView.image = self.detailItem.fullImage;
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self configureView];
}
- (void)viewDidUnload
{
[self setTitleField:nil];
[self setRateView:nil];
[self setImageView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
- (void)rateView:(RateView *)rateView ratingDidChange:(float)rating {
self.detailItem.data.rating = rating;
}
#end
This is due to following statement
SearchDataDetail *bug = [self.bugs objectAtIndex:self.tableView.indexPathForSelectedRow.row];
In this self.tableView.indexPathForSelectedRow.row will work only for only for row selection
not for accessoryButton tap.
You can solve it by two easy way
Declare a variable in .h file to store indexPath and use it to pass selected indexPath.
Pass indexPath in sender
All the best..

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.

Xcode 4.2 Xib Drop Down Menu

I have created a .xib file and I'm having a little problem.
I did a video to show you where I'm at with it. The software I am using is Xcode 4.2 and its an iOS application.
Here is a copy of the code in my view .h .m
#import <UIKit/UIKit.h>
#interface myview : UIViewController <UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate, UIPickerViewDataSource>
#property (strong, nonatomic) IBOutlet UITableView* tableView;
#property (strong, nonatomic) IBOutlet UIPickerView* pickerView;
#property (strong, nonatomic) NSMutableArray* tableData;
#property (strong, nonatomic) NSMutableArray* pickerData;
#end
#import "myview.h"
#implementation myview
#synthesize tableView, pickerView, tableData, pickerData;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
tableView.delegate = self;
tableView.dataSource = self;
pickerView.delegate = self;
pickerView.dataSource = self;
tableData = [[NSMutableArray alloc] init]; // table starts empty
pickerData = [[NSMutableArray alloc] initWithObjects:#"1", #"2", #"3", #"4", #"5", nil]; // picker starts with values 1, 2, 3, 4, 5
[tableView reloadData];
[pickerView reloadAllComponents]; // Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
//The number of sections in UITableView
return 1;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
// The number of rows in the UITableView
return [tableData count];
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [ [UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
// Set the table cell text to the appropriate value in tableDate
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Whatever happens when you select a table view row.
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
// The number of sections in the UIPickerView
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
// The number of rows in the UIPickerView
return [pickerData count];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
// The data for each row in the UIPickerView
return [pickerData objectAtIndex:row];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
// whatever you want to happen when a row is selected.
// here I am assuming you want to remove from the picker and add to the table on selection
[tableData addObject:[pickerData objectAtIndex:row]];
[pickerData removeObjectAtIndex:row];
[tableView reloadData];
[pickerView reloadAllComponents];
}
#end
I know this is super old and the video is removed so I am not sure what the exact problem is but it is most certainly related to you instantiating everything in our viewDidUnload... You should be doing this in your viewDidLoad most likely.

SplitViewController Opening Other Views in Xcode 4.2 with NSNotifications

I am trying to make a Split View Controller app and was following a recipe tutorial. However the tutorial is from before Xcode 4.2 and will not properly work. I have looked everywhere for a simple tutorial for Xcode 4.2 and story boards but could not find any. Anyways below is my code and my error any help will be greatly appreciated!
Error:
2012-01-02 16:37:40.061 blackdahlia[2233:f803] -[DetailViewController recieveNotification]: unrecognized selector sent to instance 0x6c35650
2012-01-02 16:37:40.063 blackdahlia[2233:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DetailViewController recieveNotification]: unrecognized selector sent to instance 0x6c35650'
*** First throw call stack:
(0x13be052 0x154fd0a 0x13bfced 0x1324f00 0x1324ce2 0x9c9a39 0x1389885 0x13897a8 0x90e1aa 0x91ab13 0x334b 0xa571d 0xa5952 0x92d86d 0x1392966 0x1392407 0x12f57c0 0x12f4db4 0x12f4ccb 0x12a7879 0x12a793e 0x15a9b 0x2688 0x25e5 0x1)
terminate called throwing an exception
MasterViewController.m
#import "MasterViewController.h"
#import "DetailViewController.h"
#implementation MasterViewController
#synthesize detailViewController = _detailViewController;
#synthesize anotherdetailViewController;
#synthesize recipeArray, recipe;
- (void)awakeFromNib
{
self.clearsSelectionOnViewWillAppear = NO;
self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
[super awakeFromNib];
recipeArray = [[NSMutableArray alloc] init];
recipe = [[Recipe alloc] init];
recipe.name = #"Sex on the Beach";
recipe.instructions = #"Sexy Instructions";
recipe.image = [UIImage imageNamed:#"testimage.jpg"];
[recipeArray addObject:recipe];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipeArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
recipe = [recipeArray objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
anotherdetailViewController.recipe = [recipeArray objectAtIndex:indexPath.row];
[[NSNotificationCenter defaultCenter] postNotificationName:#"Test" object:self];
NSLog(#"NOTIFICATION SENT");
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source.
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#end
DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
#property (strong, nonatomic) UIPopoverController *masterPopoverController;
- (void)configureView;
#end
#implementation DetailViewController
#synthesize detailItem = _detailItem;
#synthesize detailDescriptionLabel = _detailDescriptionLabel;
#synthesize masterPopoverController = _masterPopoverController;
#synthesize recipe, imageView;
#pragma mark - Managing the detail item
- (void)setDetailItem:(id)newDetailItem
{
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
if (self.masterPopoverController != nil) {
[self.masterPopoverController dismissPopoverAnimated:YES];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
self.detailDescriptionLabel.text = [self.detailItem description];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(recieveNotification) name:#"Test" object:nil];
}
- (void)recieveNotification:(NSNotification *) notification{
if ([[notification name] isEqualToString:#"Test"]) {
self.navigationItem.title = recipe.name;
[self.imageView setImage:recipe.image];
NSLog(#"Notification Recieved");
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
#pragma mark - Split view
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
barButtonItem.title = NSLocalizedString(#"Master", #"Master");
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
self.masterPopoverController = popoverController;
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
// Called when the view is shown again in the split view, invalidating the button and popover controller.
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
self.masterPopoverController = nil;
}
#end
Your -receiveNotification: method takes an NSNotification as an argument, but you have specified the selector for the notification to be -receiveNotification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(recieveNotification) name:#"Test" object:nil];
Note the lack of a colon. This means that the notification is actually looking for a method with the signature: - (void)receiveNotification instead of - (void)receiveNotification:(NSNotification *)notification.

Resources