Programmatically reinitializing a UIView causes it to not update - ios

I have a UIViewController which has a small scrollable window, in which I put a custom UIView called "TreeView". TreeView is blank, but has child classes of specific trees. In my code, in viewDidLoad, I am doing the following:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CGRect treeFrame = self.treeView.frame;
NSLog(#"passing tree frame of origin %f %f, size: %f, %f", treeFrame.origin.x, treeFrame.origin.y, treeFrame.size.height, treeFrame.size.width);
self.treeView = [[GelbMotiveView alloc] initWithFrame:treeFrame];
}
Then, in my test child, I load it's .xib file (successfully) and see that UIViews and subviews are added to the child. However, these views are never rendered, even if I call setNeedsDisplay. Instead, the original TreeView is rendered (I gave it a red background so I would know for sure). I can put a break point in drawRect in the test child class and it's never called.
What am I doing wrong?
Am I just doing something completely weird? Should subclasses of UIView not have their own subclasses? Is there a better way to programmatically swap out a small section of a UIViewController's screen? My intent is for the children of TreeView to render a tree/graph and make it intractable (buttons, etc, for nodes). Is there a better way to do this?

I take it that treeView is an IBOutlet and loaded from a nib?
If that is the case, the mistake you are making is reinitializing that pointer with initWithFrame: which wipes out your reference to the original view and creates a new one that isn't hooked up to anything. You can change the frame and change properties on it but you shouldn't initialize it again.

Related

Where to add UI objects when programmatically creating UI (instead of .xib/storyboard)

In the UIViewControllers where I don't use .xib files, I've been creating my UI elements in the viewDidLoad methods. E.g.,
- (void)viewDidLoad {
[super viewDidLoad];
// Setup table
self.tableView=[[UITableView alloc] initWithFrame:self.view.frame];
[self.tableView setDataSource:self];
[self.tableView setDelegate:self];
[self.view addSubview:self.tableView];
// Setup custom cells
UINib *lessonNib=[UINib nibWithNibName:#"CustomCell" bundle:nil];
[[self tableView] registerNib:lessonNib forCellReuseIdentifier:#"CustomCellID"];
}
For the most part, this is working fine. But, in the process of investigating a bug associated with dynamic cell heights, I'm curious: is this the appropriate spot to add my UI elements?
Thanks for reading.
I would say it is the right place, yes. Since this method is only called once for sure and is called before anything of your view is shown on screen. And therefore you don´t risk creating them twice or even more often or too late.
But your layout should be done at a different place - in viewWillLayoutSubviews:
When a view's bounds change, the view adjusts the position of its subviews. Your view controller can override this method to make changes before the view lays out its subviews. The default implementation of this method does nothing.
You could also overwrite the corresponding viewDidLayoutSubviews
Regarding your comment: yes, layout information is not yet present in viewDidLoad, self.view.frame for example is not guaranteed to be the actual frame that your view will be displayed in later on. Further more a frame change by some part of your code would cause your UI to not respond if you set their size and position only on load.
Note: Setting up your subviews via code is far more tedious than just designing them in a storyboard - I would heavily recommend that if you don´t have serious concerns against using them.

iOS - Where to initialize views

If I want to initialize views programmatically, where in the viewcontroller lifecycle should this happen?
The initial intuition is loadView. However, here, we don't yet have the frame of the view itself (necessary for calculating the sizes/positions of the views). Ditto for viewDidLoad.
Next intuition is viewWillAppear- here we DO (finally) have a guarantee of the frame of the view. However, this has potential to be called many times throughout the vc lifecycle. Ditto for viewDidAppear, etc...
Finally, I found viewWillLayoutSubviews. This works for the initialization of most static layouts- however, whenever any view moves this gets called again (same problem as viewWillAppear).
I've seen recommendations to init the views in loadView and set their frames in viewWillLayoutSubviews (since setting frames should be idempotent, who cares if it gets called a couple times). But then why does apple so strongly encourage initWithFrame: as the standard initialization method of UIViews (https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/CreatingViews/CreatingViews.html)?
Would it be crazy to subclass all my UIViewControllers to have an initWithViewFrame: method? That way I can pass in a frame, manually set it immediately in loadView and be done with it? Or is it better to have a viewHasBeenFormatted flag in viewWillAppear that, if not set, calls the formatting of views and then sets it?
Or is this just apple's way of saying "use interface builder or you're screwed"?
Any help is appreciated!
edit- accidentally wrote loadView where I meant viewWillAppear (in final paragraph)
update- I guess I've come to terms with the fact that there is no place where
The frame is confidently known
The code will only be run once (on setup)
Looks like you're expected to initWithFrame: all your views in viewDidLoad (but then I guess the contents of that view shouldn't treat that frame as even remotely final? because how could it be when it was derived on an assumption? ugh...). Then re-set their frames in layoutSubviews. And make sure to manually handle the differences between initial layout and layout as a result of a moved view there... Man I feel like I've GOT to be missing something... (lol denial...)
I guess that, OR submit and use IB.
update2- viewWillLayoutSubviews WILL get called when one of its subviews is resized. So it is still disqualified as it fails property 2 of the required characteristics that I'm looking for. :(
If you're doing layout with IB, it's fine to do additional view initialization in viewDidLoad (for example, if you need to do stuff that IB doesn't handle well, or if you have UIView subclasses with properties not supported by IB). Alternatively, if you're not using IB, the documentation says you should use loadView to manually initialize your view hierarchy.
You're right, though, that you can't rely on the frame being accurate at that point. So you can accomplish layout via each view's autoResizingMask property, layout constraints (if you're iOS 6 and later), and/or overriding layoutSubviews.
My usual approach is to do layout to some degree in IB, then do anything else I need to (nontrivial layout, custom classes, etc) in viewDidLoad. Then, if I have layout to figure out that autoResizingMask doesn't cover (I'm supporting down to iOS 5), I override viewWillAppear (or layoutSubviews if I'm subclassing UIView) and do some pixel math. I've got a category on UIView to help with this that has things like:
-(void)centerSubviewHorizontally:(UIView *)view pixelsFromTop:(float)pixels;
-(void)centerSubviewHorizontally:(UIView *)view pixelsBelow:(float)pixels siblingView:(UIView *)sibling;
View controllers should not have initWithFrame: methods. What I do in all of my code (I never use IB) is to let the default loadView do its own thing. I create and setup all subviews in viewDidLoad. At this point the view controller's frame has at least a sane value. All subviews can be created with their own sane frames based on the initial size of the view controller's view. With proper autoresizingMask values this may be all you need.
If you need more specific subview layout, put the appropriate layout code in the viewWillLayoutSubviews method. This will deal with any view controller view frame changes including rotation, in-call status bars, etc.
If you don't use interface builder you should override loadView and initialize the views there. If you use autolayout you can also add your constraints there. If you don't use autolayout you can override the layoutSubviews method of your views to adjust the frames.

How to change the diagrams displayed in the same view?

How to change the diagrams displayed in the same view?
I have a view inside a view controller.
This view has another view inside it, to which I have allocated a custom class (sub-classing UIView).
This custom class has the code to draw interactive diagrams in this view.
The interactive code is operated by sliders in the main view.
I have all this in interface builder.
What if I want to draw a completely different diagram in this view?
I would like to be able to allocate a new class to this view, with a different set of drawing code? But how?
There are many ways to do this, but changing the class at runtime is not advisable
Here are a few suggestions:
1 / Replace customView1 with a different view instance of the right type in the same location as your first view...
self.customView2 = [[CustomView2 alloc] initWithFrame:self.customView1.frame];
[self.view addSubView:self.customView2];
[self.customView1 removeFromSuperView];
This example uses a distinct property for each of the swapped subviews, but you could use a single property just to refer to the current subview - this could help link up your sliders to do the right thing to the diagrams. If you are doing a lot of this you will need to think about memory issues - when customView1 has gone, will you be reusing it? You can keep it hanging around in a (strong) property (faster, needs more memory), or create a new one each time (slower, needs less memory).
2 / if you want to toggle between them, you could place both in Interface Builder and toggle their hidden properties or their order in the view hierarchy (self.view.subviews array). Saves having to constantly recreate the views.
3 (better...) / Keep to a single subclass of UIView and use properties to affect the diagram that gets drawn...
//CustomView.h
#property (nonatomic,assign) BOOL drawDiagram1;
#property (nonatomic, assign) BOOL drawDiagram2;
//CustomView.m
- (void)drawRect {
if (drawDiagram1) [self drawDiagram1];
if (drawDiagram2) [self drawDiagram2];
}
- (void) drawDiagram1 {
//drawDiagram1 code here
}
- (void) drawDiagram2 {
//drawDiagram2 code here
}

Appropriate way to delete/release a UIView after removeFromSuperview

I'm playing around with drawing in iOS apps. I have a class that is a subclass of UIView that draws some lines and stuff. When the user presses a button, I instantiate the class and do an addSubView on the view of the main UIViewController of the app. The more times the user presses that button, the more instances of that class get added to the view. It's working just fine.
Now I want to provide the user a way to delete one of those views. So far I've put a [self removeViewFromSuperview] into the touchesBegan method of the custom UIView. So when the user presses the drawing it gets removed from the view. But, it's not actually deleted, right? Since the view was instantiated within the method that executes when the button is pressed I have no way to reference it from within the UIViewController. What's the appropriate way to make sure I'm not wasting memory with a UIView that was created and removed?
On a related note, if I was to put a toggle switch on the main window's UIView that toggles delete, how can I check from within touchesBegan if that toggle switch is set to delete=yes? Would I have a some sort of boolean variable in the AppDelegate that I can check from within the UIView subclass? How would I reference that?
Thank you for your help,
Stateful
If you add the view like this:
UIView *viewBeingAdded = [[[UIView alloc] init] autorelease];
[view addSubview:viewBeingAdded];
You can remove it without leaking memory:
[theViewAboutToBeRemoved removeFromSuperview];
Regarding the UISwitch, you don't need to keep its value anywhere unless you need it for something else. You can access its value directly:
if ([theSwitch isOn]) { ... }
You don't even need an IBOutlet, you can access the switch with its tag:
UISwitch *theSwitch = (UISwitch *)[view viewWithTag:<# switch tag number #>];
if ([theSwitch isOn]) { ... }
In this case you must set a unique tag number for the switch in Interface Builder or when you create it.
When you do [mainView addSubView:myView], mainView will retain myView. If you created myView with alloc/init, then you retained it also. If you don't need myView after adding it to the main view then simply do [myView release] after adding it. When you remove it from the main view, it will get released and deallocated.
If you create the UIView with alloc/init, add it to the superview then release the view, the superview will retain it. When it is removed with removeViewFromSuperview it will be dealloc'ed.
I typically autorelease a view after adding it, leaving the parent the only reference.
As to checking a toggle, you could add an IBOutlet so you can inspect it directly. (This may not be pure MVC, but I don't know if putting it in [UIApplication sharedApplication].delegate is necessarily cleaner.)

Where to place the code to change the properties of a UIView

If I'm creating a UIView programmatically and I wish to change the UIView properties (background, for example, or actually, messing with CALayers), must I place the code outside of UIView such as in the View controller? Can I put the code somewhere inside UIView?
I was checking out the CoreAnimationKioskStyleMenu example, its code is inside UIView but it's loaded from Nib and can be placed at awakeFromNib, so it doesn't seem to apply to my case.
That depends. Obviously, a good way to handle this is to use a xib file, as it is designed to hold data like this, but that isn't always the best answer for every situation.
If the view is meant to be reused frequently (like a button, or some widget) throughout the application, its best to store all that customization in a subclass of the UIView.
If its a single larger view that will always be managed by a UIViewController, you can keep some of the information in the UIViewController. However, if you end up subclassing a UIView anyway it's probably best practice to keep the data in the UIView.
As a general note, I believe its worth your time to push as much of this data into a xib using interface builder. Magic values (like colors or sizes) peppered through your code will always be a problem if you want to modify it. I have found modifying a xib to be much easier.
Actually there are some methods where you could place initialization/ customization code.
(void)willMoveToSuperview:(UIView *)newSuperview;
(void)didMoveToSuperview;
will get called as soon as u add the view as a subview to another view, at which point you already have the frame and all the properties, and you can do further customizing as you wish.
(void)layoutSubviews -- generally used for changing subviews' frames and layout organization.
Will get called each time the view needs to be redrawn by the system, or when you specifically call [self setNeedsLayout] on your UIView.
Hope this helps.

Resources