How to properly initialise controls in an instantiated view controller? - objective-c

I have a class ErrorMessageViewController:
// ErrorMessageViewController.h
#interface ErrorMessageViewController: UIViewController {
NSString * tempTitle;
NSString * tempContent;
}
#property (nonatomic, strong) IBOutlet UILabel* messageTitle;
#property (nonatomic, strong) IBOutlet UILabel* messageContent;
-(void) initTitle:(NSString *)title andContent:(NSString *)content;
#end
// ErrorMessageViewController.m
#implementation ErrorMessage
#synthesize messageTitle, messageContent;
-(void) initTitle:(NSString *)title andContent:(NSString *)content {
tempTitle = title == nil ? #"" : title;
tempContent = content == nil ? #"" : content;
}
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (messageTitle != nil) { [messageTitle setText:tempTitle]; }
if (messageContent != nil) { [messageContent setText:tempContent]; }
}
#end
And then I put it into display using this code:
NSString * errorMessage = #"Duplicate ID";
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ErrorMessageViewController * errorMessage = (ErrorMessageViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"ErrorMessage"];
[errorMessage initTitle:#"Error" andContent:contentVar];
[[self navigationController] pushViewController:errorMessage animated:YES];
When I debug it, on initTitle:Content:, the tempTitle and tempContent is NSCFConstString "Error" and NSCFString "Duplicate ID".
But then the flow goes to viewWillAppear, and both the tempTitle and tempContent 's contents are gone, replaced with NSCFString and NSObject. The tempTitle assignment is still OK because it's still string, though I don't know what the content is, but the tempContent assignment produce error. I can't even NSLog the tempContent value.
Why is this happened? Why the contents of the object change, like it was never initialised?

Set your data like this.
NSString * errorMessage = #"Duplicate ID";
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ErrorMessageViewController * errorMessage = (ErrorMessageViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"ErrorMessage"];
errorMessage.messageTitle = #"Error";
errorMessage.messageContent = errorMessage;
[[self navigationController] pushViewController:errorMessage animated:YES];
and use those variable directly in your viewDidLoad or viewWillAppear.
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Use your variables
}
Edit :
-(void) initTitle:(NSString *)title andContent:(NSString *)content {
tempTitle = (title == nil ? #"" : title);
tempContent = (content == nil ? #"" : content);
}
NSString * errorMessage = #"Duplicate ID";
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ErrorMessageViewController *vc = (ErrorMessageViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"ErrorMessage"];
[vc initTitle:#"Error" andContent:errorMessage];
[[self navigationController] pushViewController:vc animated:YES];
Hope this will help

Storyboard is use lazy loading to load the subviews. messageTitle is not initialized after called instantiateViewControllerWithIdentifier. Call self.messageTitle to make the view initialized before set value.
ErrorMessageViewController * errorMessage = (ErrorMessageViewController *)
[storyboard instantiateViewControllerWithIdentifier:#"ErrorMessage"];
//tempTitle = nil
// [errorMessage initTitle:#"Error" andContent:contentVar];
[[self navigationController] pushViewController:errorMessage animated:YES];
// set value after push
[errorMessage initTitle:#"Error" andContent:contentVar];

Related

Calling SetNeedsDisplay from other class

Today I'm facing a new problem:
I have a ViewController (Subclass of NSView) and another class (Subclass of NSObject) which, through an IBAction, try to call back the viewController to redraw its view using SetNeedsDisplay:YES.
The method to redraw the view (ViewController.m) is:
- (void) redrawView {
[self setNeedsDisplay:YES]
}
// With an NSLog i see that the method is called !
// Instead of self i tried even an outlet connected to the custom view.
}
What I'm doing to call the ViewController method through my other class is:
1) #import "ViewController.h"
2) into the IBAction i made a new istance of ViewController as:
ViewController *newIstanceOfViewController = [[ViewController alloc] init]
3) [newIstanceOfViewController redrawView]
The log show me that setNeedsDisplay is called but drawrect no ! Why ? I have forgotten to init or subview something ? Thanks
THE ORIGINAL CODE HERE (DIFFERENT NAME OF METHODS DUE TO LANGUAGE, SAME SYNTAX)
// Controller.h
#import <Cocoa/Cocoa.h>
#interface Controller : NSView {
IBOutlet NSView *tastierinoView;
}
- (void) aggiornaTastierinoView;
#end
// Controller.m
#import "Controller.h"
#implementation Controller
//Contatore numero volte disegno view
int contatore = 0;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
tastierinoView = [[NSView alloc] init];
}
return self;
}
- (void) aggiornaTastierinoView { //THIS IS THE METHOD TO CALL REDRAW
[tastierinoView setNeedsDisplay:YES];
NSLog(#"Chiamo setneedsDisplay\n\n");
}
- (void)drawRect:(NSRect)dirtyRect
{
contatore++;
NSLog(#"La view è stata disegnata %d volte\n\n",contatore);
[super drawRect:dirtyRect];
// Drawing code here.
NSBezierPath* percorso = [NSBezierPath bezierPath];
[[NSColor cyanColor] set];
[percorso moveToPoint:NSMakePoint(150, 150)];
[percorso lineToPoint:NSMakePoint(250, 150)];
[percorso setLineWidth:5.0];
[percorso stroke];
}
#end
// ManipolatorePin.h
#import <Foundation/Foundation.h>
#interface ManipolatorePin : NSObject {
IBOutlet NSWindow *finestraPrincipaleWindow;
IBOutlet NSTextField *codiceTextField;
NSArray *coordinate_x;
NSArray *coordinate_y;
//L'array sotto riportato serve, tramite un ciclo for, a salvare il codice pin spezzato in singoli numeri che corrisponderanno al punto.
NSMutableArray *numeroAllaIndexArray;
}
- (IBAction)aggiornaTastierino:(id)sender;
#end
// ManipolatorePin.m
#import "ManipolatorePin.h"
#import "Controller.h"
#implementation ManipolatorePin
- (void)awakeFromNib {
coordinate_x = [[NSArray alloc] initWithObjects:#"150",#"50",#"150",#"250",#"50",#"150",#"250",#"50",#"150",#"250", nil];
coordinate_y = [[NSArray alloc] initWithObjects:#"50",#"150",#"150",#"150",#"250",#"250",#"250",#"350",#"350",#"350", nil];
numeroAllaIndexArray = [[NSMutableArray alloc] init];
NSLog(#"Array coordinate iniziallizato.\nTest:(%#) -> deve risultare \"50\"\n\n",[coordinate_x objectAtIndex:4]);
}
- (IBAction)aggiornaTastierino:(id)sender {
NSString *codiceString = [[NSString alloc] initWithFormat:[NSString stringWithFormat:#"%#",[codiceTextField stringValue]]];
NSLog(#"codiceTextField = %#", codiceString);
int lunghezzaCodiceString;
NSLog(#"Il codice risulta essere composto da (%d) numeri\n\n",[codiceString length]);
//Svuoto array
[numeroAllaIndexArray removeAllObjects];
for (lunghezzaCodiceString = 0; lunghezzaCodiceString < [codiceString length]; lunghezzaCodiceString++) {
//Compilo array (Ci provo ahah)
NSString *carattereDelCodiceStringInEsame = [[NSString alloc] init];
carattereDelCodiceStringInEsame = [codiceString substringWithRange:NSMakeRange(lunghezzaCodiceString,1)];
NSLog(#"Aggiungo il numero (%#) all'array 'numeroAllaIndexArray'",carattereDelCodiceStringInEsame);
[numeroAllaIndexArray addObject:carattereDelCodiceStringInEsame];
}
//DEBUG - DA QUI IN POI E' CANCELLABILE
NSLog(#"\n\nCiclo for termitato\nProcesso concluso con successo\n\n\nContenuto array:");
int conteggioArray;
for (conteggioArray = 0; conteggioArray < [numeroAllaIndexArray count] ; conteggioArray++ ) {
NSLog(#"index (%d) -> (%#)",conteggioArray,[numeroAllaIndexArray objectAtIndex:conteggioArray]);
NSLog(#"\n\n");
}
//FINE DEBUG
///////////////HERE THE INSTANCE TO CALL THE CONTROLLER
Controller *istanzaGestionaleController = [[Controller alloc] init];
[istanzaGestionaleController aggiornaTastierinoView];
}
#end
It sounds like you have two unrelated instances of ViewController. You need to call redrawView on the instance that actually has a view on the screen.
In your class (which is not the NSViewController), create a property like this:
#property (nonatomic, readwrite, weak) NSViewController *vc;
When the class is instantiated:
YourClass *yourInstance = [[YourClass alloc] init];
assign the value of vc:
yourInstance.vc = self; // self is your NSViewController instance
Then when you want to trigger a redraw, you pass the command to self.vc inside your other class.

Getting null from viewController obtained from Storyboard

I'm new to storyboards. Any ideas why this might happen?
When I fire 'test' from a button within the containing view controller the log shows vc1 (as expected)
However when I 'fire' test2 from another viewController I get null..
#property (readonly, strong, nonatomic) UIViewController *vc1;
- (IBAction)test:(id)sender {
NSLog(#"log %#", _vc1 );
}
- (void)test2 {
NSLog(#"log %#", _vc1 );
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
_vc1 = [storyboard instantiateViewControllerWithIdentifier:#"test1"];
}
From your code it looks like test2 is in vc1, just do exactly as you do in vc1 in vc2

Create a UIPageViewController with a UITableView in each page

I have been reading things on stack overflow for quite a while now but this is my first post, only because it is the first time I have a problem that no one else seems to have fixed yet!
Ok, down to business. It should be a simple matter to put UITableViews inside a UIPageView but I am having difficulties. I have a ViewController and contentViewController. I am using .xibs instead of storyboarding. The contentViewController.xib is a Table View and the ViewController.xib is a View. I am only focusing on iPhone. The UITableView is connected to dataSource, delegate, and Referencing Outlet named theTableView.
The project builds but when I run it I get the following error message:
2013-03-17 16:14:23.026 pageApp[775:c07] *** Assertion failure in -[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2380.17/UIView.m:5776
2013-03-17 16:14:23.028 pageApp[775:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
*** First throw call stack:
(0x1c93012 0x10d0e7e 0x1c92e78 0xb66665 0x6539f 0x10e46b0 0x228ffc0 0x228433c 0x228feaf 0x1042bd 0x4cb56 0x4b66f 0x4b589 0x4a7e4 0x4a61e 0x4b3d9 0x4e2d2 0xf899c 0x45574 0x4576f 0x45905 0x4e917 0x20eb 0x12157 0x12747 0x1394b 0x24cb5 0x25beb 0x17698 0x1beedf9 0x1beead0 0x1c08bf5 0x1c08962 0x1c39bb6 0x1c38f44 0x1c38e1b 0x1317a 0x14ffc 0x1d2d 0x1c55)
libc++abi.dylib: terminate called throwing an exception
This crashes after -(void)viewDidLoad{} in ViewController.m and I have not yet learned how to fix auto layout/ layoutSubview errors. Does anyone else know how?
I have limited experience with ios development so I am sure that I just don't have the right pieces in the right spots. I used http://www.techotopia.com/index.php/An_Example_iOS_5_iPhone_UIPageViewController_Application to get this far.
My code is as follows:
ViewController.h
#import <UIKit/UIKit.h>
#import "contentViewController.h"
#interface ViewController : UIViewController
<UIPageViewControllerDataSource>
{
UIPageViewController *pageController;
NSArray *pageContent;
}
#property (strong, nonatomic) UIPageViewController *pageController;
#property (strong, nonatomic) NSArray *pageContent;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize pageController, pageContent;
- (contentViewController *)viewControllerAtIndex:(NSUInteger)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.
contentViewController *dataViewController =[[contentViewController alloc]initWithNibName:#"contentViewController"bundle:nil];
dataViewController.dataObject =[self.pageContent objectAtIndex:index];
return dataViewController;
}
- (NSUInteger)indexOfViewController:(contentViewController *)viewController
{
return [self.pageContent indexOfObject:viewController.dataObject];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(contentViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(contentViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageContent count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
- (void) createContentPages
{
NSMutableArray *pageStrings = [[NSMutableArray alloc] init];
for (int i = 1; i < 4; i++)
{
NSString *contentString = [[NSString alloc]initWithFormat:#"Chapter %d \nThis is the page %d of content displayed using UIPageViewController in iOS 5.", i, i];
[pageStrings addObject:contentString];
}
pageContent = [[NSArray alloc] initWithArray:pageStrings];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createContentPages];
self.pageController = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
pageController.dataSource = self;
[[pageController view] setFrame:[[self view] bounds]];
contentViewController *initialViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:pageController];
[[self view] addSubview:[pageController view]];
[pageController didMoveToParentViewController:self];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
contentViewController.h
#import <UIKit/UIKit.h>
#interface contentViewController : UIViewController
#property (strong, nonatomic) IBOutlet UITableView *theTableView;
#property (strong, nonatomic) id dataObject;
#property (strong, nonatomic) NSArray *pageContent;
#end
contentViewController.m
#import "contentViewController.h"
#interface contentViewController ()
#end
#implementation contentViewController
#synthesize theTableView, dataObject, pageContent;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void) createContentPages
{
NSMutableArray *pageStrings = [[NSMutableArray alloc] init];
for (int i = 1; i < 4; i++)
{
NSString *contentString = [[NSString alloc]initWithFormat:#"Chapter %d \nThis is the page %d of content displayed using UIPageViewController in iOS 5.", i, i];
[pageStrings addObject:contentString];
}
pageContent = [[NSArray alloc] initWithArray:pageStrings];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createContentPages];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection (NSInteger)section
{
return 4;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [pageContent objectAtIndex:indexPath.row];
return cell;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
So if anyone could straighten me out I would appreciate it.
I was able to solve my own problem, and it seems I was just a bit mixed up.
The comment from rdelmar set me on the right track but I hooked the delegate and data source to the wrong object. I had to connect them to File's Owner in order for it to work.
In addition it seems theTableView was not necessary and when I removed that my code suddenly worked as expected.
If this isn't clear enough for an answer please tell me how I can be more specific. Thank you!

After setting UITextView's text property, screen does not update accordingly

I have view controllers A(FileListViewController) and B(TextFileViewController). A is a UITableViewController. What I am doing now is that after selecting a row in controller A, I load a text file and display that text in a UITextView in controller B.
The following is the header and implementation part(some code is abridged) of my the two controllers.
FileListViewcontroller Interface:
#interface FileListViewController : UITableViewController {
NSMutableArray * fileList;
DBRestClient* restClient;
TextFileViewController *tfvc;
}
#property (nonatomic, retain) NSMutableArray * fileList;
#property (nonatomic, retain) TextFileViewController *tfvc;
#end
FileListViewController Implementation:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DBMetadata *metaData = [fileList objectAtIndex:indexPath.row];
if(!metaData.isDirectory){
if([Utils isTextFile:metaData.path]){
if(!tfvc){
tfvc = [[TextFileViewController alloc] init];
}
[self restClient].delegate = self;
[[self restClient] loadFile:metaData.path intoPath:filePath];
[self.navigationController pushViewController:tfvc animated:YES];
}
}
- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath {
NSError *err = nil;
NSString *fileContent = [NSString stringWithContentsOfFile:destPath encoding:NSUTF8StringEncoding error:&err];
if(fileContent) {
[tfvc updateText:fileContent];
} else {
NSLog(#"Error reading %#: %#", destPath, err);
}
}
And here is the interface for TextFileViewController:
#interface TextFileViewController : UIViewController {
UITextView * textFileView;
}
#property (nonatomic, retain) IBOutlet UITextView * textFileView;
-(void) updateText:(NSString *) newString;
#end
TextFileViewController implementation:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done)] autorelease];
textFileView = [[UITextView alloc] init];
}
- (void) updateText:(NSString *)newString {
NSLog(#"new string has value? %#", newString);
[textFileView setText:[NSString stringWithString:newString]];
NSLog(#"print upddated text of textview: %#", textFileView.text);
[[self textFileView] setNeedsDisplay];
}
(void)restClient: loadedFile: will be call after the loadFile:intoPath: is completed in the disSelectRowAtIndexPath method.
In TextFileViewController's updateText method, from NSLog I see that the text property is updated correctly. But the screen does not update accordingly. I've tried setNeedsDisplay but in vain. Did I miss something?
Thanks in advance.
In -[TextFileViewController viewDidLoad] you're creating a UITextView, but its frame is never set, and it's not added to the view hierarchy.
Try changing this:
textFileView = [[UITextView alloc] init];
to this:
textFileView = [[UITextView alloc] initWithFrame:[[self view] bounds]];
[[self view] addSubview:textFileView];
The problem is that textFileView is created in the viewDidLoad method of TextFileViewController. This method has not yet been called by the time you call updateText (this happens before the TextFileViewController is pushed).
You can fix this by forcing the view to load before you call [[self restClient] loadFile:metaData.path intoPath:filePath];.

UISwitch returns NULL?

I have a UISwitch that returns (null) for some reason. Below is my code:
AddAlbumViewController:
// .h
IBOutlet UISwitch *photostreamSwitch;
#property (nonatomic, retain) IBOutlet UISwitch *photostreamSwitch;
// .m
#synthesize photostreamSwitch;
photostreamSwitch = [[UISwitch alloc] init];
NSLog(#"photostreamSwitch: %#", photostreamSwitch); // returns a not-null value
SecondViewController:
//.m
- (IBAction)createAlbum:(id)sender {
AddAlbumViewController *addAlbumViewController = [[AddAlbumViewController alloc] initWithNibName:#"AddAlbum" bundle:[NSBundle mainBundle]];
NSLog(#"Test Switch: %#",addAlbumViewController.photostreamSwitch); // returns null
[addAlbumViewController release];
}
I think I have everything set up right. If this helps, AddAlbumViewController is inside a UINavigationController and SecondViewController contains the UINavigationController.
The view controller is created but its view (i.e. its nib) is not loaded yet, therefore the property isn't connected yet. You can force the nib to load by accessing the view member of the controller:
- (IBAction)createAlbum:(id)sender {
AddAlbumViewController *addAlbumViewController = [[AddAlbumViewController alloc] initWithNibName:#"AddAlbum" bundle:[NSBundle mainBundle]];
UIView* tempView = addAlbumViewController.view;
NSLog(#"Test Switch: %#",addAlbumViewController.photostreamSwitch); // no longer null
[addAlbumViewController release];
}