I've been trying to solve this bad reference for 2 days now, but I cannot find a fix.
My application is a basic to-do list with several pages (UIPageControl) for different categories and a first page for statistics about the tasks the user has finished. (if you got 3 categories, you end up with 4 pages).
It loads an array of categories from NSUserDefaults, which can be modified by a modal view accessed through a UIButton just like the (i) in the wheather app.
The Page Control is built by the ViewController class, (which is the primary view). For each element in the array of categories, the view controller generates a new TasksPageViewController class instance and instantiates a storyboard view for it.
Everything was working fine untill I put a UITableView in the TasksPageViewController view in storyboard and linked its dataSource to the same class.
After implementing the required classes for the UITableView dataSource in the TasksPageViewController, it runs fine, but if I try to go to the modal view for modifying the categories, when I press DONE (and it should reload the ViewController and rebuild the many pages for the UIPageControll), the application crashes with a EXEC_BAD_ACCESS.
Debugging with NSZombiesEnabled, I got:
2012-02-12 18:55:49.460 Pontinhos[25601:fe03] *
-[TasksPageViewController tableView:numberOfRowsInSection:]: message sent to deallocated instance 0x68c3040
But I don't really know how to proceed, I don't know which object that have been released I'm calling.
The scripts are the following:
ViewController.h
#import <UIKit/UIKit.h>
#import "MainPageViewController.h"
#import "TasksPageViewController.h"
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollView;
IBOutlet UIPageControl *pageControl;
NSMutableArray * views;
// To be used when scrolls originate from the UIPageControl
BOOL pageControlUsed;
}
#property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
#property (nonatomic, retain) IBOutlet UIPageControl *pageControl;
#property (nonatomic, retain) NSMutableArray *views;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (IBAction)changePage;
#end
ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize scrollView;
#synthesize pageControl;
#synthesize views;
//carrega views que estão na array.
- (void)viewDidLoad
{
[super viewDidLoad];
//LoadCategories
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
NSMutableArray *categories = [[NSMutableArray alloc] initWithArray:[data objectForKey:#"categories"]];
// MAIN PAGE
MainPageViewController *mainpage = [self.storyboard instantiateViewControllerWithIdentifier:#"MainPageViewController"];
CGRect mframe = scrollView.frame;
mframe.origin.x = 0;
mframe.origin.y = 0;
mainpage.view.frame = mframe;
[self.scrollView addSubview:mainpage.view];
int i = 1;
NSLog(#"Iniciando criação de páginas!");
for(id name in categories) {
NSLog(#"Carregou página.%i",i);
NSLog(#"categories count:%i",[categories count]);
TasksPageViewController *tasks = [self.storyboard instantiateViewControllerWithIdentifier:#"TasksPageViewController"];
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * i;
frame.origin.y = 0;
tasks.view.frame = frame;
[tasks populateWithData:(i-1) categoryName:name];
[scrollView addSubview:tasks.view];
tasks = nil;
i++;
}
self.pageControl.numberOfPages = [categories count]+1;
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * ([categories count]+1), self.scrollView.frame.size.height);
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (pageControlUsed)
{
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}
// Update the page when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
- (IBAction)changePage {
// update the scroll view to the appropriate page
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
[self.scrollView scrollRectToVisible:frame animated:YES];
pageControlUsed = YES;
}
// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
pageControlUsed = NO;
}
// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
pageControlUsed = NO;
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.scrollView = nil;
}
#end
TasksPageViewController.h
#import <UIKit/UIKit.h>
#import "TasksTableCellPrototypes.h"
#interface TasksPageViewController : UIViewController <UITableViewDataSource>{
IBOutlet UILabel *categoryName;
IBOutlet UITableView *tableView;
//NSMutableArray *tasksData;
}
//#property (nonatomic,retain) NSMutableArray *tasksData;
#property (nonatomic,retain) UITableView *tableView;
#property (nonatomic,retain) UILabel *categoryName;
- (void) populateWithData:(int)dataId categoryName:(NSString *)name;
#end
TasksPageViewController.m
#import "TasksPageViewController.h"
#implementation TasksPageViewController
#synthesize tableView;
//#synthesize tasksData;
#synthesize categoryName;
- (void)viewDidLoad
{
[super viewDidLoad];
// NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
//tasksData = [[NSMutableArray alloc] initWithArray:[data objectForKey:#"tasks"]];
//tasksData = [[NSMutableArray alloc] initWithObjects:#"lalal",#"lololo",nil];
}
- (void) populateWithData:(int)dataId categoryName:(NSString *)name {
//self.categoryName.text = name;
}
// Número de categorias
- (NSInteger)tableView:(UITableView *)tableViewnumberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(#"contou table view tasks");
return 4;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TasksTableCellPrototypes *cell = [[TasksTableCellPrototypes alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"taskCell"];
return cell;
}
- (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.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
Thank you.
EDIT: apparently The ViewController isn't retaining the reference to the TasksPageViewController and the UITableView cannot find its dataSource. I don't really know how to retain this using ARC.
In your ViewController viewDidLoad, when you loop through the categories you instantiate a TasksPageViewController named tasks, but then you do not retain the reference anywhere. So later the TasksPageViewController cannot be found when the tableView:numberOfRowsInSection: message is sent.
I see that in your ViewController your synthesize a property named views but then you do not use that property anywhere. Perhaps you should rename that property viewControllers and then at the end of the loop in viewDidLoad, you should add your tasks object to the viewControllers mutable array, instead of setting tasks to nil. I think that might prevent the crash.
for(id name in categories) {
TasksPageViewController *tasks = [self.storyboard instantiateViewControllerWithIdentifier:#"TasksPageViewController"];
...
[scrollView addSubview:tasks.view];
[self.viewControllers addObject:tasks];
i++;
}
Related
I have a plist (array of dictionaries) which populates a table view and works properly. I use Xcode 4 with storyboards.
Now I've created a detail view from a regular UIViewController and of course I want the selected name to be displayed in the nameLabel in the detail view. But I can't make the right connection. This is my code so far:
WineObject.m:
#import "WineObject.h"
#implementation WineObject
#synthesize libraryContent, libraryPlist;
- (id)initWithLibraryName:(NSString *)libraryName {
if (self = [super init]) {
libraryPlist = libraryName;
libraryContent = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:libraryPlist ofType:#"plist"]];
}
return self;
}
- (NSDictionary *)libraryItemAtIndex:(int)index {
return (libraryContent != nil && [libraryContent count] > 0 && index < [libraryContent count])
? [libraryContent objectAtIndex:index]
: nil;
}
- (int)libraryCount {
return (libraryContent != nil) ? [libraryContent count] : 0;
}
- (void) dealloc {
if (libraryContent) [libraryContent release];
[super dealloc];
}
#end
ViewController.h:
#import <UIKit/UIKit.h>
#class WineObject;
#interface WinesViewController : UITableViewController {
WineObject *wine;
}
#end
ViewController.m:
#import "WinesViewController.h"
#import "WineObject.h"
#import "WineCell.h"
#interface WinesViewController ()
#end
#implementation WinesViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (void)viewWillAppear:(BOOL)animated {
wine = [[WineObject alloc] initWithLibraryName:#"Wine"];
self.title = #"Vinene";
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [wine libraryCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"wineCell";
WineCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.nameLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Name"];
cell.districtLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"District"];
cell.countryLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Country"];
cell.bottleImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Image"]];
cell.flagImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Flag"]];
cell.fyldeImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Fylde"]];
cell.friskhetImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Friskhet"]];
cell.garvesyreImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Garvesyre"]];
return cell;
}
#pragma mark - Table view delegate
#end
WineCell.h:
#import <UIKit/UIKit.h>
#interface WineCell : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel *nameLabel;
#property (nonatomic, strong) IBOutlet UILabel *districtLabel;
#property (nonatomic, strong) IBOutlet UILabel *countryLabel;
#property (nonatomic, strong) IBOutlet UIImageView *bottleImageView;
#property (nonatomic, strong) IBOutlet UIImageView *flagImageView;
#property (nonatomic, strong) IBOutlet UIImageView *fyldeImageView;
#property (nonatomic, strong) IBOutlet UIImageView *friskhetImageView;
#property (nonatomic, strong) IBOutlet UIImageView *garvesyreImageView;
#end
Are you using a XIB for interface or generating it programmatically?
If you are using a XIB, the issue is that you aren't loading it up:
Change
winesDetailViewController = [[WinesDetailViewController alloc] init];
To
winesDetailViewController = [[WinesDetailViewController alloc] initWithNibName:#"YourNibNameHere" bundle:nil];
Or, if you are generating it programmatically, you must first set nameLabel or it will be nil. #synthesize doesn't set the variable, it simply generates getters and setters so that you can set it from outside.
Inside your viewDidAppear: (or better yet inside your init) add:
self.nameLabel=[[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
EDIT: If you are using Storyboards, it appears that you have to do the following.
Storyboards are all about relationships. Inside the story board editor, you add buttons and tell them which view controller they connect to. The same idea applies to TableView Cells. You can add a prototype table view cell (and customize it) and assign a relationship to it. The relationship you will want to give it is your detail view.
1.) Subclass UITableViewCell and give it a property that is the dictionary that you are trying to send to the detail view
2.) When creating cells (cellForRowAtIndexPath:) you will need to make sure to dequeue your custom cell and assign your dictionary to the property that you gave it.
3.) Make sure that your detail view has the identifier: DetailView
4.) Inside the table view controller, add the following code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"DetailView"])
{
//This works because by the time prepareForSeque: is called, the navigationController has loaded the new view
((DetailView *)[[self.navigationController viewControllers] objectAtIndex:0]).dataProperty=((MyCustomTableViewCell *)sender).dataProperty;
}
}
That ought to do it!
I have two view controllers. The CardWallet View Controller is my table view. Then the AddCard View Controller is where I input values for a new instance of an object named Card. So far, I am adding those Card instances in an array named myWallet which is in my CardWallet View Controller using a delegate and it works.
What I want is, after clicking the button in my AddCard View Controller, a new table cell will appear in my Card Wallet View, with the name depending on the recently added instance of Card. Below is my code, kindly check why is it that when I'm finished adding a new instance of Card, nothing appears in my table. I've done some research and went through some tutorials, this one is good, http://kurrytran.blogspot.com/2011/10/ios-5-storyboard-and.html, it helped me a lot regarding table view controllers. However, the tutorial doesn't cater my main concern for it's table's values only come from an array with static values.
Thanks!
CardWalletViewController.h
#import <UIKit/UIKit.h>
#interface CardWalletViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
}
#property (nonatomic, strong) NSMutableArray *myWallet;
-(void) printArrayContents;
#end
CardWalletViewController.m
#import "CardWalletViewController.h"
#import "AddCardViewController.h"
#import "Card.h"
#interface CardWalletViewController () <AddCardDelegate>
#end
#implementation CardWalletViewController
#synthesize myWallet = _myWallet;
- (NSMutableArray *) myWallet
{
if (_myWallet == nil) _myWallet = [[NSMutableArray alloc] init];
return _myWallet;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showAddCardVC"]) {
AddCardViewController *addCardVC = (AddCardViewController *)segue.destinationViewController;
addCardVC.delegate = self;
}
}
- (void)printArrayContents
{
// I want to show the name of each instance of card
for ( int i = 0; i < self.myWallet.count; i++) {
Card *cardDummy = [self.myWallet objectAtIndex:i];
NSLog(#"Element %i is %#", i,cardDummy.name );
}
}
- (void)addCardViewController:(AddCardViewController *)sender didCreateCard:(Card *)newCard
{
// insert a new card to the array
[self.myWallet addObject:newCard];
[self printArrayContents];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
}
- (void)viewWillAppear:(BOOL)animated
{
}
- (void)viewWillDisappear:(BOOL)animated
{
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//this method will return the number of rows to be shown
return self.myWallet.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Configure the cell...
//---------- CELL BACKGROUND IMAGE -----------------------------
UIImageView *imageView = [[UIImageView alloc] initWithFrame:cell.frame];
UIImage *image = [UIImage imageNamed:#"LightGrey.png"];
imageView.image = image;
cell.backgroundView = imageView;
[[cell textLabel] setBackgroundColor:[UIColor clearColor]];
[[cell detailTextLabel] setBackgroundColor:[UIColor clearColor]];
//this will show the name of the card instances stored in the array
//
for ( int i = 0; i < self.myWallet.count; i++) {
Card *cardDummy = [self.myWallet objectAtIndex:i];
cell.textLabel.text = cardDummy.name;
}
//Arrow
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
AddCardViewController.h
#import <UIKit/UIKit.h>
#import "Card.h"
#class AddCardViewController;
#protocol AddCardDelegate <NSObject>
- (void)addCardViewController:(AddCardViewController *)sender
didCreateCard:(Card *) newCard;
#end
#interface AddCardViewController : UIViewController <UITextFieldDelegate>
#property (strong, nonatomic) IBOutlet UITextField *cardNameTextField;
#property (strong, nonatomic) IBOutlet UITextField *pinTextField;
#property (strong, nonatomic) IBOutlet UITextField *pointsTextField;
#property (nonatomic, strong) id <AddCardDelegate> delegate;
#end
AddCardViewController.m
#import "AddCardViewController.h"
#import "Card.h"
#import "CardWalletViewController.h"
#interface AddCardViewController ()
#end
#implementation AddCardViewController
#synthesize cardNameTextField = _cardNameTextField;
#synthesize pinTextField = _pinTextField;
#synthesize pointsTextField = _pointsTextField;
#synthesize delegate = _delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.cardNameTextField becomeFirstResponder];
}
- (void) viewWillDisappear:(BOOL)animated
{
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField{
if ([textField.text length]) {
[self.cardNameTextField resignFirstResponder];
[self.pinTextField resignFirstResponder];
[self.pointsTextField resignFirstResponder];
return YES;
}
else {
return NO;
}
}
- (void)viewDidLoad
{
self.cardNameTextField.delegate = self;
self.pinTextField.delegate = self;
self.pointsTextField.delegate = self;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[self setCardNameTextField:nil];
[self setPinTextField:nil];
[self setPointsTextField:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)addCard:(id)sender
{
Card *myNewCard = [[Card alloc] init];
myNewCard.name = self.cardNameTextField.text;
myNewCard.pin = self.pinTextField.text;
myNewCard.points = [self.pointsTextField.text intValue];
// to check if the text fields were filled up by the user
if ([self.cardNameTextField.text length] && [self.pinTextField.text length] && [self.pointsTextField.text length])
{
[[self presentingViewController] dismissModalViewControllerAnimated:YES];
NSLog(#"name saved %#", myNewCard.name);
NSLog(#"pin saved %#", myNewCard.pin);
NSLog(#"points saved %i", myNewCard.points);
[self.delegate addCardViewController:self didCreateCard:myNewCard];
// to check if there is a delegate
if (self.delegate){
NSLog(#"delegate is not nil");
}
}
}
#end
Card.h
#import <Foundation/Foundation.h>
#interface Card : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *pin;
#property (nonatomic) int points;
#end
Card.m
#import "Card.h"
#implementation Card
#synthesize name = _name;
#synthesize pin = _pin;
#synthesize points = _points;
#end
I should get the obvious question out of the way before anyone starts dwelling too deep into this - do you have some mechanism of reloading the data after you add a new card (e.g. call [tableView reloadData] from the CardWalletViewController)? I didn't see anything like that, and I've always used this whenever I add something new to a table.*
*If the table contains too much data, you may want to reload only a part of it.
Update 1: Class Inheritance
Every Objective C class has to inherit from some other class in the hierarchy. By default, unless you say otherwise, all of your custom classes will inherit from NSObject, which is the most generic object out there (equivalent of Object, if you've done Java programming). Changing the parent class is done by simply changing the class after the : in your interface declaration. So when you say
#interface CardWalletViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
what you are saying is "declare a CardWallerViewController custom class that inherits from UIViewController and implements the UITableViewDelegate and UITableViewDataSource protocols" (if you don't know what protocols are, ask).
Now, back to your question. Changing the parent class should be easy now - you just change that : UIViewController to : UITableViewController and you are done. After you do this, your CardWallerViewController (also, "Waller", really?) will behave like a UITableView, not like a generic UIView. When doing this, you will also not need to tell it to implement the delegate and dataSource protocols - UITableViewController does that by default.
As a final note, when you add new files to your Xcode project, you can tell the program which class you want to inherit from. It defaults to UIView for views, but that's simply because this is the most generic view class. As you begin to use more specific classes (UITableViewController, UIPickerViewController, UITableViewCell, to name a few), changing the parent class off the bat will prove to be more than helpful.
Update 2: UITableViewCells
That for-loop you've got going there is a (relatively) lot of work you don't need to do. Since your table corresponds directly to your myWallet property, this means that the cell in row N of your table will represent the card at index N of your array. You can use that to your advantage. In the tableView:cellForRowAtIndexPath: method, you tell the program what to do with the cell at the specific indexPath (which is really just section + row for that table). The trick is, this method instantiates the cells one at a time. So instead of doing the for-loop, you can just say (at the end)
cell.textLabel.text = [self.myWallet objectAtIndex:indextPath.row].name;
For any cell in row N, this will look at the Nth Card object inside myWallet and use its name to set the cell's textLabel.text. If it gives you problems, save [self.myWallet objectAtIndex:indextPath.row] in some tempCard object, and then do cell.textLabel.text = tempCard.name. This is also the proper way to populate cells in a tableView - you only care about one cell at a time, because that's how the method works anyway. Imagine if you had 1,000,000 Cards inside your array - doing the for-loop would force the program to go through the array 1,000,000 times for each cell. Say hello to a 1,000,000,000,000 operations :)
i think u can add the imageview as subview to cell
UIImageView *imageView = [[UIImageView alloc] initWithFrame:cell.frame];
UIImage *image = [UIImage imageNamed:#"LightGrey.png"];
imageView.image = image;
[cell addSubview:imageView];
[[cell textLabel] setBackgroundColor:[UIColor clearColor]];
[[cell detailTextLabel] setBackgroundColor:[UIColor clearColor]];
I am trying to pass an object to a static grouped table view that I have created in story board.
Here is the code that I am using in my first view to push the second view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Row Selected");
CustomerDetailTableViewController *detailView = [[self storyboard] instantiateViewControllerWithIdentifier:#"DetailsView"];
detailView.customer = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]];
NSLog(#"%#",detailView.customer.firstName);
[self.navigationController pushViewController:detailView animated:YES];
}
The NSlog for the firstName is correct but when the detail view is pushed the cells in the detailView are null. I'm probably just missing something dumb but a fresh set of eyes would be much appreciated.
Here is the code for the detailView controller:
CustomerDetailTableViewController.h
#class Customer;
#import <UIKit/UIKit.h>
#interface CustomerDetailTableViewController : UITableViewController{
Customer *customer;
UILabel *fullName;
UILabel *address;
UILabel *homePhone;
UILabel *cellPhone;
UILabel *email;
}
#property (nonatomic, strong) IBOutlet UILabel *fullName;
#property (nonatomic, strong) IBOutlet UILabel *address;
#property (nonatomic, strong) IBOutlet UILabel *homePhone;
#property (nonatomic, strong) IBOutlet UILabel *cellPhone;
#property (nonatomic, strong) IBOutlet UILabel *email;
#property (nonatomic, strong) Customer *customer;
#end
CustomerDetailTableViewController.m
#import "CustomerDetailTableViewController.h"
#import "Customer.h"
#implementation CustomerDetailTableViewController
#synthesize fullName, address, homePhone, cellPhone, email, customer;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
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
{
fullName = [NSString stringWithFormat:#"%# %#", customer.firstName, customer.lastName];
address = [NSString stringWithFormat: #"%#/n%#, %# %#", customer.address, customer.city, customer.state, customer.zipCode];
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (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 (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
So, you have created the two views (the first TableView and the CustomerDetailTableViewController) with storyboards?
In that case, you have to click on the connection line between the two views into the storyboard and set the "Identifier" field, into the section "Storyboard Segue", to something like "setCustomer". Here's a screenshot:
Here a little screenshot:
After that, you can comment the method tableView:didSelectRowAtIndexPath: on the first TableView, and replace with this method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"setCustomer"]) {
CustomerDetailTableViewController *customerDetailVC = (CustomerDetailTableViewController *)[segue destinationViewController];
customerDetailVC.customer = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]];
}
}
Remember to include
#include "CustomerDetailTableViewController.h"
at the top of implementation file.
I hope this help!
I have a UITabBarController with three ViewControllers (A, B, and C). In ViewControllerB I have a UIScrollView. The UIScrollView consists of several instances of my PhotoViewController. These PhotoViewController objects are called from ViewControllerA, not ViewController B, where they're located.
The PhotoViewController instances have a UIImage and a two buttons. And at first, when I clicked on a button in one of my PhotoViewController instances I received a 'Thread 1: Program received signal: "EXC_BAD_ACCESS"' error. Looking around on stackoverflow that error seems to appear whenever there are memory management issues.
Since I was creating the PhotoViewController objects in a loop from a method called in ViewControllerA, and releasing those objects, I figured that by the time I switched over to ViewControllerB they were already released - and hence the memory issue.
But that's just my guess. Could you tell me if I should just stop releasing the PhotoViewController objects inside of the loop code? Because that's what I did (just commented that line out) and the program "works" fine. However, I'm still not sure if this is the proper way to handle it and if it is causing unknown memory management issues.
Here is some of my code:
ViewControllerA.m
//Creating an album in ViewControllerB, the photos in the album are PhotoViewController objects
-(IBAction)showAlbum:(UIButton *)sender
{
//Go goes here to get an album and display it in the UIScrollView
albumID = #"ALBUM_ID";
NSString* graphUrl = [NSString stringWithFormat:#"%#/photos?limit=10", albumID];
[_facebook requestWithGraphPath:graphUrl andDelegate:self];
}
...
- (void)request:(FBRequest *)request didLoad:(id)result {
//Code for array of photos
NSLog(#"%#",result);
NSString *requestType = [request.url stringByReplacingOccurrencesOfString:#"https://graph.facebook.com/" withString:#""];
if ([requestType isEqualToString:[NSString stringWithFormat:#"%#/photos?limit=10", albumID]]){
NSArray *photoAlbumArray=(NSArray*)[result valueForKey:#"data"];
[self.label setText:[NSString stringWithFormat:#"%i", [photoAlbumArray count]]];
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[ViewControllerB class]])
{
ViewControllerB *mtbvc = (ViewControllerB *)controller;
[mtbvc setArray:photoAlbumArray];
self.tabBarController.selectedIndex = 1;//switch over to the second view to see if it worked
}
}
}
...
#end
ViewControllerB.m
//loop where I create PhotoViewController objects
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
arrayCount = [array count];
scroller.delegate=self;
scroller.pagingEnabled=YES;
scroller.directionalLockEnabled=YES;
scroller.showsHorizontalScrollIndicator=NO;
scroller.showsVerticalScrollIndicator=NO;
//should have an array of photo objects and the number of objects, correct?
scrollWidth = 0;
scroller.contentSize=CGSizeMake(arrayCount*scroller.frame.size.width, scroller.frame.size.height);
for (int i = 0; i < arrayCount;i++) {
PhotoViewController *pvc = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:nil];
UIImageView *scrollImageView = [[UIImageView alloc] initWithFrame:CGRectOffset(scroller.bounds, scrollWidth, 0)];
CGRect rect = scrollImageView.frame;
pvc.view.frame = rect;
[pvc view];
pvc.label.textColor = [UIColor whiteColor];
id individualPhoto = [array objectAtIndex:i];
NSLog(#"%#",individualPhoto);
NSArray *keys=[individualPhoto allKeys];
NSLog(#"%#",keys);
NSString *imageURL=[individualPhoto objectForKey:#"source"];
//here you can use this imageURL to get image-data and display it in imageView
NSURL *url = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:url];
pvc.imageView.image = [[UIImage alloc] initWithData:data];
pvc.label.text = [individualPhoto objectForKey:#"id"];
//check to make sure the proper URL was passed
//I have an imageView next to the UIScrollView to test whether that works - it does.
[scroller addSubview:pvc.view];
[scrollImageView release];
//[pvc release];
scrollWidth += scroller.frame.size.width;
}
if (arrayCount > 3) {
pageControl.numberOfPages=3;
} else {
pageControl.numberOfPages=arrayCount;
}
pageControl.currentPage=0;
//[self.view addSubview:scroller];
}
PhotoViewController.m
#import "PhotoViewController.h"
#implementation PhotoViewController
#synthesize label, imageView;
-(IBAction)likeButton:(UIButton *)sender
{
//code goes here
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[DemoAppViewController class]])
{
DemoAppViewController *davc = (DemoAppViewController *)controller;
[davc likePicture:self.label.text];
}
}
self.tabBarController.selectedIndex = 0;//switch over to the third view to see if it worked
}
-(IBAction)skipButton:(UIButton *)sender
{
//code goes here
}
-(IBAction)likeCommentButton:(UIButton *)sender
{
//code goes here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (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];
// 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);
}
#end
PhotoViewController.h
#import <UIKit/UIKit.h>
#import "DemoAppViewController.h"
#import "MyTabBarViewController.h"
#interface PhotoViewController : UIViewController {
IBOutlet UILabel *label;
IBOutlet UIImageView *imageView;
UIButton *likeButton;
UIButton *skipButton;
UIButton *likeCommentButton;
}
#property (nonatomic, retain) UILabel *label;
#property (nonatomic, retain) UIImageView *imageView;
-(IBAction)likeButton:(UIButton *)sender;
-(IBAction)skipButton:(UIButton *)sender;
-(IBAction)likeCommentButton:(UIButton *)sender;
#end
To write iOS apps, it is critical that you understand the memory management rules.
In ViewControllerB, viewDidLoad, you alloc the pvc.
Further down, you add the pvc's view as a subview of the scroller. This retains the pvc's view, but not the pvc itself. Then when you release the pvc, it's retain count is zero, and when you reference it later, it's gone. Crash. It seems like you need to pass in and retain a reference to the pvc in the controller that's using it.
I am not sure why you are using PhotoViewController(Subclassing UIViewController) instead of PhotoView(subclassing UIView). As you are not using any of the facility of the viewcontroller(no life cycle method and other).
If you subclass PhotoViewController with UIView and remove the viewcontroller's methods it will work and will not cause any memory issues as you have aleready discussed in your discussion with Rayfleck. ( it will be retained by parent view controller.)
If you are thinking about the events, then these will also be handled by view itself. But if you want to handle it in your controller then you can easily delegate, or pass your controller reference and invoke the event on it.
Thanks,
Hy,
There is no problmen with changing the nib but when I'm in the nib-File with the OPGL ES view my buttons that are integrated in this view won't work. When I press one the application crashes.
This is the Code how I change the View to the Second Nib:
- (IBAction)drawEAGLView:(id)sender {
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"settingsViewController" owner:self options:nil];
EAGLView* glView = [ nibViews objectAtIndex: 1];
self.view = glView;
[glView startAnimation];}
The Animation is working .
This is the Code of the ViewController where the Button is declared:
SettingsViewController.h:
#import <UIKit/UIKit.h>
#interface SettingsViewController : NSObject {
IBOutlet UIButton *wowButton;
}
#property(nonatomic,retain) IBOutlet UIButton *wowButton;
- (IBAction)wowButtonPressed:(id)sender;
int testvarAnimation;
int animNext;
#end
SettingsViewController.m:
#import "SettingsViewController.h"
#implementation SettingsViewController
#implementation SettingsViewController
#synthesize wowButton;
- (IBAction)wowButtonPressed:(id)sender{
NSLog(#"TOUCHED");
//testvarAnimation = 1;
animNext =1;
}
- (void)dealloc {
[wowButton release];
[super dealloc];
}
#end
Here I show you how I connected the Functions and the Outlets:
http://s3.imgimg.de/uploads/screenshotConna91ea645png.png
Please tell me If you need more informations.
Thanks
Hy I solved my problem, I rewrote my drawEAGLView Methode:
- (IBAction)drawEAGLView:(id)sender {
SettingsViewController *sViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[[self.view superview] addSubview:sViewController.view];
}
I also corrected my SettingsViewController.h:
#import <UIKit/UIKit.h>
#class EAGLView;
#interface SettingsViewController : UIViewController {
IBOutlet UIButton *wowButton;
EAGLView *glview;
}
#property(nonatomic,retain) IBOutlet UIButton *wowButton;
#property (nonatomic, retain) IBOutlet EAGLView *glView;
- (IBAction)wowButtonPressed:(id)sender;
int testvarAnimation;
int animNext;
#end
And last but not least I changed my SettingsViewController.m:
#import "SettingsViewController.h"
#import "EAGLView.h"
#implementation SettingsViewController
#synthesize wowButton, glView;
- (IBAction)wowButtonPressed:(id)sender{
NSLog(#"TOUCHED");
//testvarAnimation = 1;
animNext =1;
}
- (void)viewDidLoad {
glView.animationInterval = 1.0 / 60.0;
[glView startAnimation];
[super viewDidLoad];
}
- (void)dealloc {
[wowButton release];
[super dealloc];
}
#end
I also changed my Nib file. I removed the ViewController and set the type of the File's Owner to my viewController named "SettingsViewController". I connected the File's Owner Outlet view with the EAGLView and the button with the SettingsViewControllers wowButtonPressed Method
I hope I could help also someone else,
Thanks
Here is the right solution for this problem:
First you have to create a parnet object of your view you want to be shown as first.
You need to do this in the header file of the second view.
//
// SettingsViewController.h
// NeheTutorial2
//
// Created by Global Sound on 05.04.11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
#class EAGLView;
#class mobilesynthViewController;
#interface SettingsViewController : UIViewController {
IBOutlet UIButton *wowButton;
IBOutlet UIButton *optiButton;
EAGLView *glView;
//mobilesynthViewController *mViewController;
mobilesynthViewController *parent;
}
#property(nonatomic,retain) IBOutlet UIButton *wowButton;
#property(nonatomic,retain) IBOutlet UIButton *optiButton;
#property (nonatomic, retain) IBOutlet EAGLView *glView;
//#property(nonatomic,retain) IBOutlet mobilesynthViewController *mViewController;
#property (nonatomic, retain) mobilesynthViewController *parent;
- (IBAction)wowButtonPressed:(id)sender;
- (IBAction)optiButtonPressed:(id)sender;
int testvarAnimation;
int animNext;
int musicOn;
int isAnimating;
float zaehler;
#end
Now in the implemetaion File you need to add the parent object to your function:
#import "SettingsViewController.h"
#import "EAGLView.h"
//#import "mobilesynthViewController.h"
#implementation SettingsViewController
#synthesize wowButton, optiButton, glView;//,mViewController;
#synthesize parent;
- (IBAction)wowButtonPressed:(id)sender{
NSLog(#"TOUCHED");
[parent startThread];
[parent showbButtonStop];
testvarAnimation = 0;
animNext =1;
//musicOn =1;
}
- (IBAction)optiButtonPressed:(id)sender{
NSLog(#"OPTIONS ON");
[self dismissModalViewControllerAnimated:YES];
[parent showbButton];
//mobilesynthViewController *aRootViewController = [[mobilesynthViewController alloc] initWithNibName:#"mobilesynthViewContoller" bundle:nil];
//[self setRootViewController:aRootViewController];
//[aRootViewController release];
//mobilesynthViewController *mViewController = [[mobilesynthViewController alloc] initWithNibName:#"mobilesynthViewController" bundle:nil];
//[[self.view superview] addSubview:mViewController.view];
//self.view = mViewController;
//CGRect rect = [[UIScreen mainScreen] applicationFrame];
//mViewController = [[mobilesynthViewController alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)];
//self.view = mViewController;
}
- (void)viewDidLoad {
glView.animationInterval = 1.0 / 60.0;
[glView startAnimation];
[super viewDidLoad];
}
/*
- (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.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
*/
- (void)dealloc {
[wowButton release];
[optiButton release];
[super dealloc];
}
#end
In my case I'm calling a other function in the main View of my program. When the button in the second view is pressed the function in the first view is called through the the parent object
[parent startThread];
Again the startThread methode was declerated in another view.
I hope I could help you.