I am trying to pass a string between two view controllers in a storyboard, core data program. The way I have it set up I want the next view to be pushed only for one section only. Hence the use of "didSelectRowAtIndexPath" rather than "prepareForSegue". Below is my code for "didSelectRow…" "five" is the viewController class being pushed.
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
five *five=[[five alloc]init];
five.delegate = self;
[self performSegueWithIdentifier:#"segue1" sender:self];
}
}
"two" is the parent view. Below is the method used in a protocol created in the "five" class. "friendString" is in the "two" class and "fiveString" in "five". When the view is popped, the strings should be the same and then I use the string added in "five" in a UITextField in "two". However it doesn't update when the view is popped.
- (void)popFive:(five *)controller
{
self.friendString=controller.fiveString;
[self update];
[self.tableView reloadData];
}
I believe the problem is related to how the seque is done in "didSelectRow…" Any help or ideas will be appreciated. Thanks.
I think when using "didSelectRow..." as a segue you also have to use "prepareForSegue". When I added this it seemed to be able to pass strings between the two view when the child view was popped. I included the code below
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
[self performSegueWithIdentifier:#"segue1" sender:self];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"segue1"])
{
five *five = segue.destinationViewController;
five.delegate = self;
}
}
Related
DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
#property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(#"Timer fired %#", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(#"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.
I assume that it is the completion block of the animation that calls respawnDemoWithSelector and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running
animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done to the DemoViewController which is set
to YES in the free method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: #selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
weakSelf does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf inside the blocks do just nothing.
First, apologies if the answer is quite obvious but I'm brand new to iOS development (like this is the first app I'm trying to program even though it's just for me to play with :P) so the probability that my problem is rather minor is quite high.
Code (zip file containing the xcode project):
http://www.mediafire.com/?p55xw0q2dwwwwvm
I promise there's nothing else in there that's harmful :).
Problem:
I'm following:
http://www.techotopia.com/index.php/An_iPhone_iOS_6_Storyboard-based_Collection_View_Tutorial
I'm attempting to make a UICollectionView to play around with. I got down to the "Testing the Application" section. I put my own spin on a few parts from the guide:
instead of using different pictures, I just use 1 picture with a label at the bottom that just has a number in it. The number is dynamically set. Yes, I could implement it without the array I'm using, but having that array there will be helpful if I extend this project's scope in the future.
I added code in viewDidLoad to set the view layout to a flow layout. This was done again for futureproofing since once I get this working at a basic form I want to play around with formatting which will require me to subclass flowlayout.
The code compiles without errors but nothing shows up on the screen. I've checked the code to the best of my ability for about an hour or two, but nothing I did made any difference. My controller class is a bare bones attempt to just get the collection view to show up on screen, and my cell class is a bare bones cell with just an imageview and label in it.
Thanks for any help that can be given to get this working!
tl;dr
Just look at the bolded stuff. Code provided below for convenience:
MyCollectionViewController.m
#import "MyCollectionViewController.h"
#interface MyCollectionViewController ()
#property (nonatomic, strong) NSMutableArray *dataArray;
#end
#implementation MyCollectionViewController
#synthesize dataArray = _dataArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Create data to display
for(int i = 0; i < 50; i++){
[self.dataArray addObject:[NSString stringWithFormat:#"%d", i]];
}
// Configure Layout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.collectionView setCollectionViewLayout:flowLayout];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark UICollectionViewDataSource
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.dataArray count];
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
int row = [indexPath row];
UIImage* image = [UIImage imageNamed:#"200px-AND_ANSI"];
myCell.cellTitle = [self.dataArray objectAtIndex:row];
myCell.cellImageView.image = image;
myCell.backgroundColor = UIColor.whiteColor;
return myCell;
}
MyCollectionViewcontroller.h
#import <UIKit/UIKit.h>
#import "MyCollectionViewCell.h"
#interface MyCollectionViewController : UICollectionViewController <UICollectionViewDataSource, UICollectionViewDelegate>
#end
MyCollectionViewCell.h
#import <UIKit/UIKit.h>
#interface MyCollectionViewCell : UICollectionViewCell
#property (strong, nonatomic) IBOutlet UIImageView *cellImageView;
#property (strong, nonatomic) IBOutlet UILabel *cellTitle;
#end
MyCollectionViewCell.m
#import "MyCollectionViewCell.h"
#implementation MyCollectionViewCell
#synthesize cellImageView = _cellimageView;
#synthesize cellTitle = _cellTitle;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
self.dataArray is nowhere allocated/initialized in your code and therefore is equal to nil. Sending messages to nil is allowed, but has no effect, therefore even after
for(int i = 0; i < 50; i++){
[self.dataArray addObject:[NSString stringWithFormat:#"%d", i]];
}
self.dataArray is still nil, and [self.dataArray count] returns 0.
You have to allocate and initialize the array with
self.dataArray = [[NSMutableArray alloc] init];
A proper place is some initXXX method of the view controller. But initWithNibName: is not called if the view controller is instantiated from a storyboard file, you have to use initWithCoder: instead:
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.dataArray = [NSMutableArray array];
}
return self;
}
I have been struggling with something for weeks and it has brought a real halt to my progress. I have asked a question a few times on SO, people have been helpful but no-one has cracked what I am doing wrong. It seems a fairly simple thing so hopefully someone out there will have a lightbulb moment and solve this. I am implementing a TWRequest, the result is coming back in a dictionary, I am looping through the results to extract a part of the tweet and creating an array of these 'text' components. Straight adter the loop through I am peinting the log of the array - _twitterText and it prints fine. Staright after this method is complete it seems as though _twitterText is being dumped. I have created it in my .h file as a strong property and created an ivar in viewdidload. Still no joy. how do I retain this array to use in another Method?
Here is my .h file....
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "CustomCell.h"
#import "AppDelegate.h"
#import <Twitter/Twitter.h>
#interface MyViewController : UITableViewController <CLLocationManagerDelegate>
{
CLLocationManager *here;
}
#property(strong) NSDictionary *dict;
#property(strong) CLLocationManager *here;
#property (strong, nonatomic) NSMutableArray *twitterText;
- (void)fetchTweets;
#end </p>
Here is my .m implementation file......
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
#synthesize dict;
#synthesize twitterText = _twitterText;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_twitterText = [[NSMutableArray alloc] init];
here = [[CLLocationManager alloc] init];
here.delegate = self;
[here startUpdatingLocation];
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSLog(#"phrase carried over is %#", delegate.a);
[self fetchTweets];
}
- (void)fetchTweets
{
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
#"http://search.twitter.com/search.json?q=%40wimbledon"]
parameters:nil requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
}
NSLog(#"MY ************************TWITTERTEXT************** %#", _twitterText);
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
//cell.venueDetails.text = [_twitterText objectAtIndex:indexPath.row];
NSLog(#"MY ************************OTHER BIT THAT WONT PRINT************** %#", _twitterText);
return cell;
}
So the issue here is that your completion handler that you pass to -[TWTweet performRequestWithHandler:] isn't going to (can't) fire until the network connection is complete and the server responds to your request. That could take hundreds of milliseconds or even seconds to complete. (Or it may never happen).
Meanwhile while that is happening, the UITableView wants to draw itself and so will ask you how many sections/rows you have and then will ask you for a cell for each row. So when the table view asks, you should return the actual number of rows you have to draw at that time:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.twitterText count]; // the actual number of rows we have right now
}
So then the next step you need is to reload the table when your data is actually in from the server. That will prompt your table view to ask again for the number of sections and rows, and then ask for cells for each section and row. So somewhere in your completion block after you've processed all your data you will need to do this:
dispatch_async(dispatch_get_main_queue(), ^{
// you'll need an outlet to the UITableView
// here I assume you call that 'tableView'
// then just ask it to reload on the main thread
[self.tableView reloadData];
});
I hope that helps?
I'm using CoreData to store objects like cars, trips, data recorded from GPS, etc.
When I fetch what I want to show a list of trips, some stats for a trip, or add a new car in my settings view controller, I use pretty much this kind of request:
- (void)getDataTrip
{
// Fetched data trips.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DataTrip" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idTrip = %#", self.idTrip];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"failed with error: %#", error);
}
// Set the array.
[self setDataTripArray:mutableFetchResults];
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
Sometimes, I have leaks when I do the [self setDataTripArray:mutableFetchResults]; and sometimes not. In this case, when I get the data for a trip, it leaks all the time when I use the navigation controller to come back to the root view controller (displaying a list of trips), and/or when I change tab.
Anyway, it just leaks and it's all the time coming from fetching data from CoreData, and give this array to my local array var.
Please let me know if you see how to fix this! It made the app crash after a while.
Thanks!
SOLUTION
I found that I do a retain on my object dataTripArray object when creating another UIViewController that I use to create graphs for my scroll view.
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// Replace the placeholder if necessary.
GraphController *controller = [self.graphControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[GraphController alloc] initWithPageNumber:page data:[self.dataTripArray retain]];
[self.graphControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// Add the controller's view to the scroll view.
if (controller.view.superview == nil)
{
CGRect frame = _scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[self.scrollView addSubview:controller.view];
}
}
I just removed the retain and the leak is no longer coming up. Fixed!
I have a problem using the -(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control method. I tried many ways to test it but it just won't appear. Can anyone find a mistake
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)anno{
MKPinAnnotationView *retval=nil;
if(retval ==nil){
retval=[[[MKPinAnnotationView alloc]initWithAnnotation:anno reuseIdentifier:#"Annotations"]autorelease];
UIButton *disclosure=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
retval.rightCalloutAccessoryView=disclosure;
[retval setPinColor:MKPinAnnotationColorGreen];
retval.animatesDrop=YES;
retval.canShowCallout=YES;
}
return retval;
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control{
NSLog(#"Tap");
PhotoDetail *detail=[[PhotoDetail alloc]initWithNibName:#"PhotoDetail" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:detail];
[self presentModalViewController:nav animated:YES];
}
Make sure you implemented the
MKMapViewDelegate protocol
in the parent ViewController.