Appropriate way to delete/release a UIView after removeFromSuperview - ios

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.)

Related

Where to customise IBOutlets

Where should I customise my IBOutlets?
Say I have created a button with interface builder, created an IBOutlet for it and I would want to change a property during runtime (ex: background color or localized title).
I would think of adding it to the viewDidLoad method, but outlets aren't yet created.
I remember having nil outlets in viewDidLoad, but I might be wrong.
If I move it viewWillAppear, the code will be executed every time the view controller's view appears.
Is there any better place for my IBOutlet related code, so it's only executed once?
Obviously I can do just about any customization using only the interface builder and making use of the User defined runtime attributes or localized stroryboards, but I don't like that since it's much more tedious to change later.
From the Doc
Its clearly says about the Views loaded into the memory in the -viewDidLoad() delegate itself.
I would think of adding it to the viewDidLoad method, but outlets
aren't yet created.
It is a false statement, Because you only get the viewDidLoad: message after IBOutlets are created. So you can safely do any customization in viewDidLoad:
Let’s say you have a Button you want to customise. You put the button at the place where you want it to be and then open the “Identity Inspector” on the right.
There is a textfield for “Custom Class”:
I usually create a subclass of UIButton / NSButton (depending on iOS or OSX) and edit the behaviour, drawing methods and functionality in this class file. Then just add the name of this class in this textfield. Voila!

How can I clear all data in a view and reload it?

I have game that is played on a single view, when the game is over I want the user to be able to press a button (play again) that will completely reload the view (clearing all game data and refreshing the view as if it were loading for the first time). I have tried
[self.view setNeedsDisplay]
however nothing is happening. Do I have to manually clear out the data or is there a way to reset everything at once?
What I've similarly done in this case is to create a property which holds all of your game subviews, etc.
#property (nonatomic, strong) UIView *gameView;
Then in viewDidLoad, we call a method that sets up our game view for the first time (You will see we will use the same method a little later again for resetting the game)
- (void)viewDidLoad
{
[super viewDidLoad];
[self setUpGame];
}
- (void)setUpGame
{
// Your game views (subviews, buttons, etc.) set up here
self.gameView = [[UIView alloc] initWithFrame:self.view.bounds];
}
Then, when the user taps the play again button, we simply remove the game view from the superview, and call the prior method we discussed, which sets up the game again. Your button's target should be set to call this method below: [self playAgain];
- (void)playAgain
{
[self.gameView removeFromSuperview];
// This is the method we previously discussed above
[self setUpGame];
}
It's up to you to think of some cool and unique animations to make it a pleasing resetting of the game at this point :)
setNeedsDisplay just indicates that you would like iOS to redraw the screen, which you rarely should need to call manually.
I would probably implement something like #troop231 already stated, which is a reset method, but I would not re-allocate any buttons / views etc because that could be costly. In the MVC model, you should have your data (scores, # of lives, etc.) stored separately and your views should just reference them. So, reset the model and assuming you have your views aware of model changes, they will update accordingly.
Ways to do this include KVO, NSNotification, Core Data's NSFetchedResultsController (probably overkill), delegation, etc.

Can I connect xib object to any viewController?

I know it is possible to connect an object in a XiB file (i.e button) and connect it to any viewController. I am also able to go to that viewController and programmatically set properties to that object(everything autocompletes fine, it recognizes the object properties) However, when I run the app, the button is unchanged, what gives?
Is there something I'm missing? Is there an additional step that I need to do when using a ViewController that is not the .m file related to the XIB?
Here's part of the code... I don't get any errors!
user.default_sales_rep_id = 2;
if (user.default_sales_rep_id > 0) {
buttonMask.backgroundColor = [UIColor blackColor];
}
You are most likely setting the properties on the button too early. Since you don't specify in your question where this code is located, it's hard to say but I'd guess if you put your code in awakeFromNib it would work.
- (void)awakeFromNib {
[super awakeFromNib];
//code here
}
Any changes to your view that differ from your XIB should be done in this method as it is called after the view is set up from the XIB.
Are you certain you are calling [[UIButton alloc] init] before you attempt manipulating it? I assume you have the button already as an IBOutlet, but if I recall, if you wish to make custom changes to the button, you must still do this.

if scrollview inherits from UIView how come my code below doesn't work?

if scrollview inherits from UIView how come my code below doesn't work?
DOBMonthTextField us an an IBOutlet for a UITextField.
[DOBMonthTextField setHidden:YES];
It stopped working when I made my UIVIEW underneath the text field a scrollview. The text field use to hide when I executed the code above. Now it doesn't hide.
While changing the views in nib files via cut-paste, the IBoutlet connections of those views go missing (disconnected), so you always have to reconnect them after pasting.
Check your connections whenever some code for a view created in nib/storyboards used to work earlier but does not after some modifications.
Because hidden (or its setter, setHidden:) is a property of an instance of DOBMonthTextField, not the class itself. Classes don't have properties in Objective C. Here's an example of roughly what you should be doing instead:
DOBMonthTextField *someInstance = [[DOBMonthTextField alloc] init];
[someInstance setHidden:YES];

UIBarButtonItem: frame treated differently for target-action call vs. performSelector:withObject:

I have a UIBarButtonItem *button. The idea is that the user presses the button and then a window pops out. This is declared in the target/action of the button (i.e. tapping the button calls
(void)showMyWindow:(id)sender
where the sender is the UIBarButtonItem.
In the showMyWindow: method, the drawing of the pop-up window requires the frame of the sender. Now, UIBarButtonItem doesn't typically allow you to access its frame. As a bit of a hack, I've cast the sender to a UIView and then accessed this UIView's frame. I didn't think this would work but, surprisingly, it does.
However, I also want to call the showMyWindow: method elsewhere. So I have this line of code:
[self performSelector:#selector(showMyWindow:) withObject:self.button];
Here, my app crashes. I've pinpointed the problem exactly to this:
(void)showMyWindow:(id)sender
{
//I should be checking before the cast here, but it helps illustrate the problem
UIView *senderAsView = (UIView *)sender
CGRect frame = senderAsView.frame;
...
}
The point is: I can somehow perform this cast+access the frame when I call the method with target-action, but not when I use performSelector:withObject:
Why is there a difference? Why can this cast be performed in one case but not the other?
Thanks.
As a bit of a hack, I've cast the sender to a UIView and then accessed this UIView's frame. I didn't think this would work but, surprisingly, it does.
UIBarButtonItem is descended from NSObject, not UIView, and does not have a frame property. The sender in this case is most likely not your bar button item, but either a private view belonging to it (if you are using a system item) or the custom view property of it.
When you call it "manually" you really are sending the UIBarButtonItem instance, which does not have a frame, and will crash when you cast it to a UIView and ask it for the frame property.
You can clarify what is really being sent by examining the sender object in the debugger. It will be a view subclass in the first instance, and a UIBarButtonItem (or subclass) in the second.

Resources