I'm really new to iPhone programming.
This app is a simple quiz. FirstAppDelegate.m creates an instance of QuizViewController and adds its view to the window.
#import "FirstAppDelegate.h"
#import "ResultViewController.h"
#import "QuizViewController.h"
#implementation FirstAppDelegate
#synthesize window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions {
UIViewController *vc = [[QuizViewController alloc] init];
[window addSubview:[vc view]];
[window makeKeyAndVisible];
[vc release];
return YES;
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
I thought I could release vc like I do hear since window will retain it (?) but it generated an error :
2011-06-28 23:06:34.190 First[14289:207] -[__NSCFType foo:]: unrecognized selector sent to instance 0x4e1fc90
2011-06-28 23:06:34.193 First[14289:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFType foo:]: unrecognized selector sent to instance 0x4e1fc90'
...so I commented it and now it works fine. But where should I release vc? Here's QuizViewController.h:
#import <UIKit/UIKit.h>
#interface QuizViewController : UIViewController {
IBOutlet UILabel *questionLabel;
IBOutlet UIButton *button1;
IBOutlet UIButton *button2;
IBOutlet UIButton *button3;
int currentQuestionIndex;
int corrects;
NSMutableArray *questions;
NSMutableArray *answers;
NSMutableArray *correctAnswers;
}
- (IBAction)foo:(id)sender;
#end
...and QuizViewController.m:
#import "QuizViewController.h"
#implementation QuizViewController
- (id)init {
NSLog(#"QuizViewController init");
[super initWithNibName:#"QuizViewController" bundle:nil];
questions = [[NSMutableArray alloc] init];
answers = [[NSMutableArray alloc] init];
correctAnswers = [[NSMutableArray alloc] init];
[questions addObject:#"Vad betyder det engelska ordet \"though\"?"];
[answers addObject:#"Tuff"];
[answers addObject:#"Dock"];
[answers addObject:#"Tanke"];
[correctAnswers addObject:#"Dock"];
[questions addObject:#"Vad hette frontpersonen i Popbandet Queen?"];
[answers addObject:#"Pierre Bouviere"];
[answers addObject:#"Freddie Mercury"];
[answers addObject:#"Stevie Wonder"];
[correctAnswers addObject:#"Freddie Mercury"];
return self;
}
- (IBAction)foo:(id)sender {
NSLog(#"foo");
}
- (void)loadView {
NSLog(#"QuizViewController loadView");
[questionLabel setText:[questions objectAtIndex:currentQuestionIndex]];
[button1 setTitle:[answers objectAtIndex:currentQuestionIndex] forState:UIControlStateNormal];
[button2 setTitle:[answers objectAtIndex:currentQuestionIndex + 1] forState:UIControlStateNormal];
[button3 setTitle:[answers objectAtIndex:currentQuestionIndex + 2] forState:UIControlStateNormal];
[super loadView];
}
- (void)viewDidLoad {
NSLog(#"QuizViewController viewDidLoad");
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[super dealloc];
}
#end
You should create an instance variable to hold the VC. The reason you are losing it when you release it is that the window is only retaining the view and not the controller.
I thought I could release vc like I do hear since window will retain it...
Notice that you are adding the view associated to the view controller ([vc view]) to your UIWindow. That object will be retained, not your controller.
You can fix this by defining a variable in your FirstAppDelegate to store the controller there and release it in FirstAppDelegate dealloc.
#interface FirstAppDelegate
.....
#property (nonatomic, retain) QuizViewController* controller;
.....
#end
#implementation FirstAppDelegate
#synthesize window;
#synthesize controller;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions {
self.controller = [[QuizViewController alloc] init] autorelease];
[window addSubview:[vc view]];
[window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
....
[controller release]; controller = nil;
....
}
The views/windows do retain their child views, the view controllers retain their views, but the views don't retain their controllers. It's a "one-way" relationship, a clear has-a. This also comes in handy to prevent retain cycles.
You probably want to save the controller in an ivar in the class you alloc/init it, and release it in dealloc or when you pull the view from screen.
View controllers often get retained by other view controllers, i.e. when you push them onto a navigation stack, or put them in tabs.
If you don't mind dropping support for iOS 3.0/3.1/3.2, you can use the UIWindow.rootViewController property:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions {
UIViewController *vc = [[[QuizViewController alloc] init] autorelease];
window.rootViewController = vc;
[window makeKeyAndVisible];
return YES;
}
Related
I am getting this error when running the app:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity
name 'Event''
This is my TimeTableController.h file (TimeTableController is a subclass UITableViewController and app loads this file when app stared.
TimeTableController.h:
#import "Event.h"
#interface TimeTableController : UITableViewController
{
NSManagedObjectContext *managedObjectContext;
NSMutableArray *eventArray;
}
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *eventArray;
- (void) fetchRecords;
- (void) addTime:(id)sender;
#end
Event.h and Event.m are my model files.
And TimeTableController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Lap Times";
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addTime:)];
self.navigationItem.rightBarButtonItem = addButton;
//[addButton release];
[self fetchRecords];
// 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)fetchRecords {
// This line gives the error
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext];
}
The line in fetchRecords method is giving the error.
And this is my AppDelegate.m file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
TimeTableController *tableController = [[TimeTableController alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
//[tableController release];
[window addSubview: [self.navigationController view]];
[window makeKeyAndVisible];
return YES;
}
When I've created the project,Data Model file didn't exist so I've added the Data Model file (LapTimer.xcdatamodeld) to existing project.
I tried all solutions I found and I have no idea why I am getting this error now.
Note: I know there are a lot of threads that cover exactly this questions. I looked at all but none of answers didn't work for me.
I was getting the same error message and it turns out that managedObjectContext was nil.
I'm developing an iOS 4 application using latest SDK, XCode 4.2 and ARC.
I've added a method to appDelegate.h
#import <UIKit/UIKit.h>
#class ViewController;
#class SecondViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
UINavigationController* navController;
ViewController* viewController;
SecondViewController* secondViewController;
}
#property (strong, nonatomic) UIWindow *window;
- (void) showSecondViewController;
#end
And it's implemented in appDelegate.m
#import "AppDelegate.h"
#import "ViewController.h"
#import "SecondViewController.h"
#implementation AppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
viewController.title = #"First";
navController = [[UINavigationController alloc] initWithRootViewController:viewController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
...
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
...
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
...
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
...
}
- (void)applicationWillTerminate:(UIApplication *)application
{
...
}
- (void) showSecondViewController
{
secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.title = #"Second";
[navController pushViewController:secondViewController animated:YES];
}
#end
But, when I send a message to that method in ViewController.m
- (IBAction)goSecondClicked:(id)sender
{
[[[UIApplication sharedApplication] delegate] showSecondViewController];
}
I get the following compiler error:
Automatic Reference Counting Issue
No known instance method for selector 'showSecondViewController'
Any clue?
You will need to cast the delegate object that you get as:
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Then call the method on appDelegate
Change your goSecondClicked action method to this:
- (IBAction)goSecondClicked:(id)sender
{
[[[UIApplication sharedApplication] delegate] performSelector:#selector(showSecondViewController)];
}
EDIT: although this alternative works for the given situation, it should be noted that the compiler won't help you if you change the method name in your delegate and forget to change the name on the selector call. So, this should be used carefully.
You can also define this macro on your AppDelegate.h
#define APP_DELEGATE (AppDelegate *)[[UIApplication sharedApplication] delegate]
After this, you can invoke your selector with:
[APP_DELEGATE showSecondViewController];
I went through the material regarding this issue but still can't figure it out completely.
The main issue is that i am using this method but the view does not show...
The app is built out of two view - the main one (i added it as a subview to the main nib) -
Has of a text field where i write some text.
Has a button that when pressed, the action method loads a second view that has a label with some text in it.
the source code of the first viewController:
#import <UIKit/UIKit.h>
#class TxtRunnerViewController;
#interface Itai_s_text_app_take_2ViewController : UIViewController {
int sliderSpeed;
IBOutlet UITextField *textInput;
TxtRunnerViewController *trvc;
}
#property (nonatomic, retain) IBOutlet UITextField *textInput;
#property (retain, nonatomic) TxtRunnerViewController *trvc;
- (IBAction)sliderChanged:(id)sender;//speed of text show changed
- (IBAction)textFieldDoneEditing:(id)sender;//dor 'DONE' on keyboard
- (IBAction)backgroundTap:(id)sender;//for handling tapping on background
- (IBAction)textEmButtonPressed:(id)sender;
#end
the .m of the first ViewController:
#import "Itai_s_text_app_take_2ViewController.h"
#import "TxtRunnerViewController.h"
#implementation Itai_s_text_app_take_2ViewController
#synthesize textInput;
#synthesize trvc;
- (IBAction)sliderChanged:(id)sender
{
UISlider *slider = (UISlider *)sender;
sliderSpeed = (int)(slider.value + 0.5f);//setting the speed determinned by the usr in slider
NSLog(#"Slider value is %#", sliderSpeed);
}
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
NSLog(#"our text input is %#", textInput.text);
}
- (IBAction)backgroundTap:(id)sender
{
[textInput resignFirstResponder];
}
- (IBAction)textEmButtonPressed:(id)sender
{
NSLog(#"button pressed");
if ([textInput.text length]==0)
{
NSString *msg = nil;
msg = #"Write text to transmit";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Forgot something?"
message:msg
delegate:self
cancelButtonTitle:#"Back"
otherButtonTitles:nil];
[alert show];
[alert release];
[msg release];
}
else { //init
NSLog(#"HI!");
if (self.trvc == nil)
{
NSLog(#"if accepted");
TxtRunnerViewController *tr = [[TxtRunnerViewController alloc] initWithNibName:#"TxtRunner"
bundle:nil];
self.trvc = tr;
[tr release];
}
[self.view removeFromSuperview];
[self.view insertSubview:trvc.view atIndex:0];
NSLog(#"InsertAtIndex was operated...!");
//[self.view addSubview:tvc.view];
}
}
/*
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (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.
if(self.trvc.view.superview == nil)
self.trvc = nil;
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[trvc release];
[super dealloc];
}
The second View is simply empty (has a label added to it in it's nib file):
#import
#interface TxtRunnerViewController : UIViewController {
}
#end
It's .m file is the default one, added no code to it.
When i press the button in the first view - it's view disappears but the second view does not appear instead, only a blank white view appears, nothing on it.
Doe
If you push the TxtRunnerViewController it will completely hide the first one, but it still exist in background. (without removeFromSuperview)
TxtRunnerViewController *vw = [[TxtRunnerViewController alloc]initWithNibName:#"TxtRunner" bundle:nil];
[self.view pushViewController:vw.view animated:NO];
[vw release];
To remove the first view it's a bit more difficult. You would need to implement a delegate in RootViewController which will be fired after clicking the done Button of the first view. Then in your RootViewController some code is executed which displays the second view and removes the first view.
For a delegate sample open the Utility Application Sample when creating a new project and figure out how it works or search some examples online.
The line:
[self.view removeFromSuperview];
Removes the view from its super
then the call
[self.view insertSubview:trvc.view atIndex:0];
Adds trvc to it - but that view has been removed and not visible anymore.
You need to add trvc to the superview.
I am just looking at ways to access a simple model object (in the MVC sense) from my controller. Right now I am creating the model in the applicationDelegate, and passing it to the controller when I create the controller.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Setup Model
DataModel *tempDataModel = [[DataModel alloc] init];
[self setDataModel:tempDataModel];
[tempDataModel release];
// Setup Controllers
Controller *rootController = [[Controller alloc] initWithModel:[self dataModel]];
UINavigationController *tempNavController = [[UINavigationController alloc] initWithRootViewController:rootController];
[self setNavController:tempNavController];
[rootController release];
[tempNavController release];
[window addSubview:[[self navController] view]];
[window makeKeyAndVisible];
return YES;
}
inside the controller I have:
#property (nonatomic, retain)DataModel *dataModel;
and:
- (id)initWithModel:(id)newModel {
self = [super init];
if(self) {
NSLog(#"%s", __PRETTY_FUNCTION__);
dataModel = [newModel retain];
}
return self;
}
- (void)dealloc {
NSLog(#"%s", __PRETTY_FUNCTION__);
[dataModel release];
[super dealloc];
}
This works fine, but I am just curious if this is ok in terms of MVC and good design. In previous apps I have:
Used a shared instance (Singleton)
Created the model from inside the controller.
Any comments would me much appreciated:
I think this is perfectly good design. The controller is allowed to manipulate the model, so needs a reference to this. I think your current way of injecting the Model instance is better than a singleton approach.
I am just curious with regards to the correct way to create a view controller programatically. When I compile this code with the static analyser I get a leak (as you would expect) from the alloc. Should I just leave it as it needs to stay until the app exits anyways, or is there a cleaner way?
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(#"UIApplication application:");
RectViewController *myController = [[RectViewController alloc] init];
[window addSubview:[myController view]];
[window makeKeyAndVisible];
return YES;
}
cheers Gary
In this case, keep a reference to your view controller as an instance variable on the AppDelegate and release it in the AppDelegate's dealloc method.
#interface AppDelegate : NSObject {
// ...
RectViewController *myController;
}
// ...
#end
#implementation AppDelegate
// ...
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(#"UIApplication application:");
myController = [[RectViewController alloc] init];
[window addSubview:[myController view]];
[window makeKeyAndVisible];
return YES;
}
- (void) dealloc {
// ...
[myController release];
[super dealloc];
}
// ...
#end
Keep a reference to the view controller in you app delegate (instance, property, synthesize and release in dealloc).
And then instantiate it like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(#"UIApplication application:");
RectViewController *rootControllerTemp = [RectViewController new];
self.rootController = rootControllerTemp;
[rootControllerTemp release];
[window addSubview:[self.rootController view]];
[window makeKeyAndVisible];
return YES;
}