Why setNeedsDisplay required when initWithNibName used - ios

I have custom UIViewController class called MSPageViewController with and associated nib file. I have an IBOutlet which is a UIImageView called pageImage.
Now, I want to use this view controller in another UIViewController which will display a series of my custom MSPageViewController in a UIPageViewController. So, I use the following code:
// alloc and init my custom view controller
MSPageViewController *page1 = [[MSPageViewController alloc] initWithNibName:#"MSPageViewController" bundle:nil];
// I must call this or, the image that I set below will always be null
// why? I guess it's because the view hasn't been drawn yet because it hasn't been displayed, so I need to force the redraw - but this is my question. Is this is the right approach?
[page1.view setNeedsDisplay];
// set the image
page1.pageImage.image = [UIImage imageNamed:#"tutorialPage1.png"];
// make my array of view controllers, it expects an array because could be double-sided
NSArray *viewController = [NSArray page1];
// pass the array that contains my custom view controller
[self.pageController setViewControllers:viewController direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
So am I doing this right? I have to force the redraw so that my outlets exist when I try to assign to them?

It's not the setNeedsDisplay part that you "need", it's the self.view part. By accessing the view property you are forcing the view controller to actually load the NIB. I guess that as a side effect of this, the pageImage property is populated as well (and was nil before you called self.view).
So, just calling self.view; instead of [self.view setNeedsDisplay]; should be enough.

As others have noted, pageImage (a UIImageView?) is likely not loaded from the nib yet when you're accessing it.
If you have a custom getter for pageImage, you could do the following:
- (UIImageView*) pageImage
{
[self view];
return _pageImage; // assuming the property backing ivar is synthesized as _pageImage.
}
But my personal preference would be to not expose the imageview itself and just expose a property for image. Then you can set the image to the viewcontroller regardless of it's loaded state, and internally set it to the imageView once the view loads:
- (void) setImage: (UIImage*) image
{
_image = image;
if ( self.isViewLoaded )
{
self.pageImage.image = image;
}
}
- (void) viewDidLoad
{
[super viewDidLoad];
self.pageImage.image = self.image;
}

If you moved some code to the viewDidLoad method you would be guaranteed that the view had been drawn.

Related

Remember my current view

I actually wrote a method which switches some UIViews. When I enter the method I have to know which is my current view to remove exactly this one from the superview. I declared a property for this purpose in the header file:
#property (weak, nonatomic) UIView *currentView;
After I changed to a certain view in my method, I want to assign this view to the property in order to use it when I enter the method again and remove this view from the superview if requested. The method looks like this:
...
nextView = [self loadNextView]; // the view is loaded from a NIB here
if (self)
{
[self viewWillDisappear:YES];
[[self currentView] removeFromSuperview];
[[self view] addSubview:nextView];
[self currentView] = [self nextView];
...
Unfortunately the compiler tells me that currentView is not assignable here. Is there another, hopefully better, way to remeber the curernt view for my purpose or should I use a completely different way?
Instead of the following line,
[self currentView] = [self nextView];
do as follows:
[self setCurrentView:[self nextView]];
You can't assign to the value of a function or method call (it returns an rvalue). You have to use the property accessor syntax:
self.currentView = [self nextView];
or explicitly invoke the setter:
[self setCurrentView:[self nextView]];
Try this
Replace
[self currentView] = [self nextView];
with
self.currentView = [self nextView];

Conflict between Interface builder and .m#.h : who win?

I've been wondering for a few time now how does Xcode interpret the use of both IB and programmaticaly coded objects.
ex : I initWithStyleGrouped a table in .m but I set plain as the style in the attributes of the tableviewcontroller I am working on. so? I noticed that the code gets over the IB.
It first appears when I had to custom a detail table by insering a header and a UITextField in the first cell which is very easy with IB. But when I run the app, nothing but the template of a plain table.
gnuh??
Thank you for your help.
Cheers,
Louis
EDIT
here is the instantiate of the TableViewController :
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
They should never cross. If you instantiate a table with [[UITableView alloc] initWithStyle:UITableViewStyleGrouped], it creates a new table without going though a NIB. If you instantiate a table from a NIB, it creates an instance using -initWithCoder:.
Added after Update
OK, you are subclassing UITableView. In addition to overriding -initWithStyle:, you will want to override -initWithCoder: or -awakeFromNib.
The basic flow of loading an custom UIView from a NIB.
-initWithCoder: is used to instantiate the object
All NIB connections are make (IBOutlets and IBAction are connected).
-awakeFromNib is send to the object
This means if you set a value in -initWithCoder:, the NIB setting will win; if you set a value in -awakeFromNib, the your code will win.
Be sure to read the Subclassing Notes and Methods to Override sections of UIView.
It all depends on how you initialize the object. If you load the UITableView from your controller's init method and call initWithStyle, that's what your UITableView will be. If you want to use IB's, you'll need to initialize your controller with initWithNibName and have an IBOutlet connection to your view, which has the plain setting.
Well, I am not sure but I think I found a beginning of solution. here are my thoughts.
I consider a IB oriented design.
the compiler will ask for IB to instantiate the view.
But if we created a UITableViewController subclass, then we have all the method refering to the instantiation (correct word?) of this view.
So, in order to avoid this conflict, We can erase the code in the .M which refers to the initialization of the table : initWithStyle and the pragma mark about Table source. we just let the View life cycle needed by any view and the delegate.
I found some exemple using this. here is the .m of a detail view table which is designed with static cells on IB :
#import "PictureListDetail.h"
#implementation PictureListDetail
#synthesize managedObjectContext;
#synthesize currentPicture;
#synthesize titleField, descriptionField, imageField;
#synthesize imagePicker;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// If we are editing an existing picture, then put the details from Core Data into the text fields for displaying
if (currentPicture)
{
[titleField setText:[currentPicture title]];
[descriptionField setText:[currentPicture desc]];
if ([currentPicture smallPicture])
[imageField setImage:[UIImage imageWithData:[currentPicture smallPicture]]];
}
}
#pragma mark - Button actions
- (IBAction)editSaveButtonPressed:(id)sender
{
// If we are adding a new picture (because we didnt pass one from the table) then create an entry
if (!currentPicture)
self.currentPicture = (Pictures *)[NSEntityDescription insertNewObjectForEntityForName:#"Pictures" inManagedObjectContext:self.managedObjectContext];
// For both new and existing pictures, fill in the details from the form
[self.currentPicture setTitle:[titleField text]];
[self.currentPicture setDesc:[descriptionField text]];
if (imageField.image)
{
// Resize and save a smaller version for the table
float resize = 74.0;
float actualWidth = imageField.image.size.width;
float actualHeight = imageField.image.size.height;
float divBy, newWidth, newHeight;
if (actualWidth > actualHeight) {
divBy = (actualWidth / resize);
newWidth = resize;
newHeight = (actualHeight / divBy);
} else {
divBy = (actualHeight / resize);
newWidth = (actualWidth / divBy);
newHeight = resize;
}
CGRect rect = CGRectMake(0.0, 0.0, newWidth, newHeight);
UIGraphicsBeginImageContext(rect.size);
[imageField.image drawInRect:rect];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Save the small image version
NSData *smallImageData = UIImageJPEGRepresentation(smallImage, 1.0);
[self.currentPicture setSmallPicture:smallImageData];
}
// Commit item to core data
NSError *error;
if (![self.managedObjectContext save:&error])
NSLog(#"Failed to add new picture with error: %#", [error domain]);
// Automatically pop to previous view now we're done adding
[self.navigationController popViewControllerAnimated:YES];
}
// Pick an image from album
- (IBAction)imageFromAlbum:(id)sender
{
imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
[self presentViewController:imagePicker animated:YES completion:nil];
}
// Take an image with camera
- (IBAction)imageFromCamera:(id)sender
{
imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
[self presentViewController:imagePicker animated:YES completion:nil];
}
// Resign the keyboard after Done is pressed when editing text fields
- (IBAction)resignKeyboard:(id)sender
{
[sender resignFirstResponder];
}
#end
available here : enter link description here
What do you think ??
Even though I now alone on this post, I wanted to post the answer !
I finally figured out why my textfield didn't appear which show who's has the hand on the compiler.
I actually subclassed the detail with a class wich implement methods to fill the table source with the coreData.Then, the supposed static cells were actually filled with these methods.
Which shows, to my humble opinion, that the .m overpass the IB.

UILabel, UITextView etc.. work only in ViewWillAppear and ViewDidLoad, and don't change dynamically

i have a problem, i want set text of a UILabel or UItextView or another IBOUTLET objects,but i can do it only in the viewwillappear and viewdidload method, if i set text in another method in the code don't change nothing, here is an example:
i have a method that retrieve string from another class, and then i want set this string in my uilabel:
- (void)setDetailItem:(id)newDetailItem
{
if (detailItem != newDetailItem) {
NSLog(#"SetDetailItem");
[detailItem release];
detailItem = [newDetailItem retain];
detailString = [[FindStringClass alloc] init];
// Update the view.
detailItem = [detailString searchStringFor:detailItem.name];
if (detailItem) {
//
NSLog(#"setDetail: %#",detailItem.stringName);
[self configureView];
}
}
}
- (void)configureView {
NSLog(#"configure view: %#",detailItem.stringName);
mySerialTitle.text = detailItem.stringName;
}
the NSLog work, and i can see my string in the console, but the text in view don't change, instead if i set a simple text in the ViewWillAppear or viewDidLoad method work, so the connection with the IBOOutlets are right, like this:
- (void)viewDidLoad
{
mySerialTitle.text = #"CIAO";
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
This is the call function where i call the setDeatItem from another view:
self.searchResultViewController.detailItem = [myArray objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:self.searchResultViewController animated:YES];
anyone can help me?
EDIT:
This is the NSLog where the method are called:
2012-03-19 12:51:02.617 TestApp[292:b603] SetDetailItem
2012-03-19 12:51:06.161 TestApp[292:b603] setDetail: Chicken
2012-03-19 12:51:06.162 TestApp[292:b603] configure view: Chicken
2012-03-19 12:51:06.175 TestApp[292:b603] View DidLoad
i call the searchviewcontroller view from a Tableviewcontroller i press on a row and then call the new view and pass the attribute.
viewDidLoad is called after the view is loaded into memory. See viewDidLoad in the apple docs. After the view is loaded into memory and right before it appears, viewWillAppear is called.
You cannot change the properties of the view before it is loaded. So, examine your code paths (and maybe even log out in those methods and the viewDidLoad and viewWillAppear method) and see if you're setting those properties before viewDidLoad is called (log output will make it easy to see).
From viewDidLoad docs:
This method is called after the view controller has loaded its view
hierarchy into memory. This method is called regardless of whether the
view hierarchy was loaded from a nib file or created programmatically
in the loadView method. You usually override this method to perform
additional initialization on views that were loaded from nib files.
You're log statements that you added makes it clear that you are manipulating the views before they are loaded. As you can see, setDetail is called before viewDidLoad.
2012-03-19 12:51:02.617 TestApp[292:b603] SetDetailItem
2012-03-19 12:51:06.161 TestApp[292:b603] setDetail: Chicken
2012-03-19 12:51:06.162 TestApp[292:b603] configure view: Chicken
2012-03-19 12:51:06.175 TestApp[292:b603] View DidLoad
If you want to set the data on the view you're pushing, you have a couple options.
Call setDetail but in setDetail only set iVar data - don't manipulate views. Then, in viewDidLoad/WillAppear, read the iVar data and manipulate the views.
Use delegates to have the view you're pushing call back to the launching view. See What exactly does delegate do in xcode ios project?
- (void)setDetailItem:(id)newDetailItem
{
if (detailItem != newDetailItem) {
NSLog(#"SetDetailItem");
[detailItem release];
detailItem = [newDetailItem retain];
detailString = [[FindStringClass alloc] init];
// Update the view.
detailItem = [detailString searchStringFor:detailItem.name];
if (detailItem) {
//
NSLog(#"setDetail: %#",detailItem.stringName);
[self configureView];
}
}
}
the above function wherever you are calling it form..i don't know.. make sure you call it after
[self.navigationController pushViewController:self.searchResultViewController animated:YES];
if you call it before this.. then your view has not loaded properly and setting text to a label won't work..

Why is this UIView not presenting in front of a UITableView?

I have a UINavigationController/UITableView and I want to present a UIView over top of it when the table is empty to give the user a prompt on how to add items to the table.
I've never make a UIView (as opposed to a UIViewController before) so I'll step through what I did to make it:
Make a new UIView Class - MakeSentenceHelperView
Make a nib called MakeSentenceHelperView.xib
Set File's owner to MakeSentenceHelperView
Load the nib in the MakeSentenceHelperView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"makesentencehelperview init");
[[NSBundle mainBundle] loadNibNamed:#"MakeSentenceHelperView" owner:self options:nil];
}
return self;
}
and present the MakeSentenceHelperView in the UITableViewController:
//present the placeholder view for sentences
MakeSentenceHelperView *makeSentenceHelperView = [[MakeSentenceHelperView alloc] init];
NSLog(#"present placeholder: self.navigationcontroller.view: %#", self.navigationController.view);
//Something like this:
[self.navigationController.view addSubview:makeSentenceHelperView];
[self.navigationController.view bringSubviewToFront:makeSentenceHelperView];
The class loads and logs ok, but nothing appears in front of the UITableView - where have I gone wrong?
UPDATE: if I add [self.tableView setHidden:YES]; then the tableview disappears and the space is black and empty. I'm assuming this means I'm setting up the View wrong, somewhere.
You can use https://github.com/ecstasy2/toast-notifications-ios/ for showing Toast view liek Android. Check array size and if table view is not showing then called this one and show any custom method.
Thanks to #Aadhira for their link which led me to the problem.
I needed to add awakeFromNib
I was missing [self addSubview:self.view]; at the end of initWithFrame.

Simple UIView drawRect not being called

I can't figure out what the problem is here. I have a very simple UIViewController with a very simple viewDidLoad method:
-(void)viewDidLoad {
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
[self.view addSubview:v];
[super viewDidLoad];
}
And my GameView is initialized as follows:
#interface GameView : UIView {
and it simply has a new drawRect method:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
NSLog(#"drawing");
}
In my console, I see "making game view" being printed, but "drawing" never is printed. Why? Why isn't my drawRect method in my custom UIView being called. I'm literally just trying to draw a circle on the screen.
Have you tried specifying the frame in the initialization of the view? Because you are creating a custom UIView, you need to specify the frame for the view before the drawing method is called.
Try changing your viewDidLoad to the following:
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
if (v == nil)
NSLog(#"was not allocated and/or initialized");
[self.view addSubview:v];
if (v.superview == nil)
NSLog(#"was not added to view");
[super viewDidLoad];
let me know what you get.
Check if your view is being displayed. If a view is not currently on screen, drawRect will not be getting called even if you add the view to its superview. A possibility is that your view is blocked by the some other view.
And as far as I know, you don't need to write [super drawRect];
Note that even if viewDidLoad is called on a view controller it doesn't necessarily indicate the view controller's view is displayed on screen. Example: Assume a view controller A has an ivar where a view controller B is stored and view controller A's view is currently displayed. Also assume B is alloced and inited. Now if some method in A causes B's view to be accessed viewDidLoad in B will be called as a result regardless whether it's displayed.
If you're using a CocoaTouch lib or file you may need to override the initWithCoder method instead of viewDidLoad.
Objective-C:
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
//Do Stuff Here
}
return self;
}
I had a view that was offscreen, that I would load onscreen and then redraw. However, while cleaning up auto-layout constraints in XCode, it decided my offscreen view should have a frame (0,0,0,0) (x,y,w,h). And with a (0,0) size, the view would never load.
Make sure your NSView has a nonzero frame size, or else drawRect will not be called.

Resources