In my project I used storyboard and when I accessed an UI element I created a property and linked it. This property is weak. To my understanding the property can be weak since it is already added to the view and the views retains it.
In an other project I do not use storyboard. Now I am not sure how to define a UI element. I think this can be done both, situation 1:
#interface LoginView
#property (strong, nonatomic) UIButton *login
#end
- (instancetype) init {
if (self == [super init]) {
_login = [UIButton buttonWithType:UIButtonTypeCustom];
[self addSubview:_login];
[self setNeedsUpdateConstraints];
}
return self;
}
Situation 2:
#interface LoginView
#property (weak, nonatomic) UIButton *login //<<notice weak
#end
- (instancetype) init {
if (self == [super init]) {
UIButton login = [UIButton buttonWithType:UIButtonTypeCustom];
[self addSubview:login];
_login = login
[self setNeedsUpdateConstraints];
}
return self;
}
My question is: "Can both situation be used? If so is there a preferred way?"
Own ideas: I think situation two is preferred since it doesn't create a second strong pointer?
Either will work fine.
Personally I prefer strong, so that I'm not relying on another view retaining something.
Also strong has slightly less overhead as opposed to a weak reference which needs to be tracked and zeroed to nil automatically (not that you would notice this time difference).
You can still use weak reference because the views super view will be having strong reference to it.
Related
I need to programmatically delegate various events which occur in the main view to handlers that are instantiated by the main ViewController.
In other words, instead of having the ViewController handle all the events for a given view I would like to have separate instantiated objects handle the events for subsections of the overall view.
A numbered bulleted list describing how to implement this, or a link to a numbered bulleted list would be highly helpful. If you included an explanation which used examples from Java's Swing API as analogous operations you would earn a special place in my heart.
Here is my current half-baked code. I include it so you know I have tried to solve this before resorting to Stack Overflow.
TCH_MainViewController.m
#import "TCH_MainViewController.h"
#import "TCH_MainViewButtonHandler.h"
#interface TCH_MainViewController ()
#property (nonatomic, weak) IBOutlet UILabel *titleLabel;
#property (nonatomic, weak) TCH_MainViewButtonHandler *buttonHandler;
#property (nonatomic, weak) UIButton *changeColorButton;
#end
#implementation TCH_MainViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[_changeColorButton addTarget:_buttonHandler action:#selector(changeColorButton) forControlEvents:UIControlEventTouchUpInside];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
TCH_MainViewButtonHandler.m
#import "TCH_MainViewButtonHandler.h"
#implementation TCH_MainViewButtonHandler
- (IBAction)changeLabelColor:(id)sender{
}
-(void)awakeFromNib{
}
#end
you do have the selector signature wrong, but you also need to initialize your handler class first either directly or lazily. Oh yea, and the property for the handler should be strong, not weak as you need your main class to retain the handler class. I'm assuming the button is on a Storyboard or Xib file. In that case it's good for the button to be weak because the Storyboard is retaining it strongly. If you class did as well, that would create a retain cycle.
Correct the property:
#property (nonatomic, strong) TCH_MainViewButtonHandler *buttonHandler;
Initialize the class before you set the selector on the button.
self.buttonHandler = [TCH_MainViewButtonHandler alloc]init];
note: probably a better practice to access the property via self rather then its instance _button..
then set the target on the button:
[self.changeColorButton addTarget:self.buttonHandler action:#selector(changeColorButton:) forControlEvents:UIControlEventTouchUpInside];
Note: the colon on the end of the selector is usually added if you let Xcode autocomplete. It references the parameter "sender" in this case.
Add a little NSLog(#"button pressed"); in the button action in your button handler and see if that does not get called. It should.
Now, normally UI events like button pushes are not normally delegated away because they end up doing something to the view and that needs to come from the controller managing the view. Although for a network call you might do it. If your button push is affecting the view i.e updating an label, you'll need to come back to the view controller for that purpose. So you have to think through that.
However, per your question, this answer should get you to the next step.
hope that helps,
best wishes.
Your selector is #selector(changeColorButton) when I believe it should be #selector(changeLabelColor:).
I have a UIViewController,which is associated with custom class MAViewControllerMenu and loads right after the splash screen. In that UIViewController, I have an UIScrollView, which belongs to another class, MASlideShowView, in which the IBOutlet of the UIScrollView is defined and is connected to.
The class for the UIViewController has, among others, the field:
#property MASlideShowView* slideShow;
as a private property for the class that holds the UIScrollView inside it.
Also in the UIViewController,
- (void)viewDidLoad
{
[super viewDidLoad];
//TODO [_slideShow initializeImages];
_slideShow = [[MASlideShowView alloc] initWithModel];
_slideShow.delegate = imageViewController;
}
- (void)viewDidAppear{
[super viewDidAppear:(YES)];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set up the content size of the scroll view
//HERE, self.slideShow is allocated, but all the fields it has, including the IBOutlet to the UIScrollView is still nil
CGSize pagesScrollViewSize = self.slideShow.frame.size;
_slideShow.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageViews.count, pagesScrollViewSize.height);
//Delegate
_slideShow.scrollView.delegate = self;
// Load the initial set of pages that are on screen
[_slideShow loadVisiblePages:YES page_index:0 image:_last_image_taken];
}
Note the error I saw in the comments in the above class
The MASlideShowView file looks like:
h:
#class MASlideShowView;
#protocol slideShowDelegate <NSObject>
-(void)imageViewSelected:(MASlideShowView*)slideShow image:(UIImage*)image;
#end
#interface MASlideShowView : UIScrollView
#property (nonatomic,weak) id<slideShowDelegate> delegate;//delegate to next controller to notify upon picture centered
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIPageControl *pageControl;
#property (weak, nonatomic) IBOutlet UIButton *rotateImageButton;
#property UIImageView* centered_image_view;
- (IBAction)PageThroughPageControl;
- (IBAction)rotateImageButtonClicked;
- (id)initWithModel;
- (void)pageThroughPageControl;
- (void)addImageToSlideshow:(UIImage*)toAdd;
- (void)loadVisiblePages:(BOOL)use_page_number page_index:(NSInteger)page image:(UIImage*)image;
#end
m:
- (id)initWithModel{
[self initializeImages];
return self;
}
-(void)initializeImages{
// Set up the image you want to scroll & zoom and add it to the scroll view
self.pageViews = [NSMutableArray arrayWithObjects:nil];
NSInteger pageCount = 0;
_imageViewCount = 0;
// Set up the page control
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = pageCount;
// Set up the array to hold the views for each page
self.pageViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
}
My question is simple:
How can I make the UIScrollView initialize?
I know that there's no viewDidAppear as it inherits from UIScrollView.
Thanks
As you are using Interface Builder, I would recommend calling initializeImages inside awakeFromNib:
An awakeFromNib message is sent to each object loaded from the
archive, but only if it can respond to the message, and only after all
the objects in the archive have been loaded and initialized. When an
object receives an awakeFromNib message, it is guaranteed to have all
its outlet instance variables set.
More details here.
Other observations:
As for your code, you have slideShow correctly set by Interface Builder when entering viewDidLoad but you're replacing that instance by assigning _slideShow = [[MASlideShowView alloc] initWithModel], which results in a completely different object.
Moreover your initWithModel doesn't look at all like a correct init method as it doesn't call any of its super's init methods. You should start with Apple's snippet by writing init in an empty line and press escape:
Again the first paragraph of the answer should be enough for your problem.
There's a few ways you could go about fixing this.
One way is like #HoanNguyen mentioned to use awakeFromNib. Personally I don't use this but it's a valid lifecycle event for setup.
Another option is to override initWithCoder: which is the standard initializer storyboards use
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeImages];
}
return self;
}
You could then remove your initWithModel call and the storyboard should handle everything.
I have 2 view controller,
FirstViewController - > SecondViewController via
[self presentViewController:SVC animated:YES completion:nil];
on SecondViewContrller when I do
[self dismissViewControllerAnimated:YES completion:nil];
My question is, Why is the objects not release on secondViewController after I dismiss this viewcontroller. As you can see on the graph It didn't go down after dismiss. BTW whats the best way to release/dismiss a ViewController?
[EDIT]
I NSLog a message on dealloc method on every VC, When I start from FVC->SVC->[dismiss SVC]. this is my logs
This can be pretty rough stuff. I had similar issues before. Search your code and see if you have strong or wrong references to objects.
One of my top mistakes (and what I have seen on the internet hundreds of times) are delegate properties. I wrote them like #property (nonatomic, retain) id<protocol>delegate; for quite a long time as I realized that if I do so, the delegated object does not get released. One have to use assign in this case.
Hope that help you...
I have made some investigation with this behavior.
FirstViewController.m
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)dealloc {
NSLog(#"First Dealloc");
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
UIButton *pressMe=[UIButton buttonWithType:UIButtonTypeCustom];
pressMe.frame = CGRectMake(0, 0, 100, 40);
pressMe.center = self.view.center;
[pressMe setTitle:#"PressMe" forState:UIControlStateNormal];
[pressMe addTarget:self action:#selector(pressMeAction:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:pressMe];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) pressMeAction:(id) sender
{
SecondViewController *svc = [[SecondViewController alloc] init];
[self presentViewController:svc animated:YES completion:nil];
NSLog(#"Present Second");
}
#end
SecondViewController.m
is pretty the same except
-(void) pressDissmissButtonAction:(id) sender
{
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Dismiss Second");
}
and this is Allocation dynamics
As you can see after pressMeButtonAction invoked secondViewController allocated and after pressDissmissButtonAction invoked secondViewController is successfully deallocated.
BUT: Most of the time it deallocated immediately, but if you present and dismiss it very quickly (twice a second or so), dellocation not fired immediately, but after a while.
I Assume that this is by design implementation of ARC deallocation procedure. Not sure.
try this ...
[self presentViewController:SVC animated:YES completion:nil];
SVC = nil;
After spending many hours on this, I finally found a missing piece of the puzzle: Not only do you have to set any strong references to the ViewController to nil, you also have to invalidate any timers and be aware of block retain cycles.
Any time you use self in a block you create a retain cycle! Instead you should declare a variable like so
__unsafe_unretained ViewController *weakSelf = self;
and use it instead of self in the block.
Check all IBOutlets in your application. There might be "strong" property assigned to them. Make them "weak". For example, an IBOulet should be like this:
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
Check all delegates (if any) in your application. Every delegate should be like this:
#property (nonatomic, assign) id <yourProtocol> delegate;
Note that, it takes some amount of time for ARC to recover memory.
Timers were the issue in my case. Added timer invalidate to viewWillDisappear and the view controllers were then released.
I have a UIViewController with a single button and an activity indicator.
In the class for this VC MainViewController.m I do the following in viewDidAppear:
- (void)viewDidLoad
{
[super viewDidLoad];
_actLoadLoc.color = [UIColor blueColor];
_startButton.enabled = NO;
[_startButton setTitle:#"Fetching Location" forState:UIControlStateDisabled];
}
Another method in my MainViewController.m is called readyToGo and is implemented as follows:
-(void) readyToGo
{
[NSThread sleepForTimeInterval:1.0f];
NSLog(#"Done sleeping");
_startButton.enabled = YES;
[_startButton setTitle:#"Start" forState:UIControlStateNormal];
_actLoadLoc.stopAnimating;
}
I have properties for both UIButton, UIActivityIndicatorView and a declaration of the readyToGo method in my MainViewController.h as follows:
#property (weak, nonatomic) IBOutlet UIButton *startButton;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *actLoadLoc;
-(void) readyToGo;
The readyToGo method is called from another class abc.[h/m] which imports MainViewController.h. The call happens after one of the functions in abc.m completes filling an array with calculated data.
The call works since Done Sleeping shows in the output, however the startButton is not enabled, its test does not change and the actLoadLoc does not stop animating... Any idea what's wrong with my code/method?
Thanks in Advance!
You are calling the readyToGo on the wrong instance of the view controller. You have an instance which is displaying content on the screen and you are, in some way, creating a new one to call the method on. You need to get the existing one instead.
It's not ideal, but you should be able to get the controller with:
UINavigationController *n = (UINavigationController *)[UIApplication sharedApplication].keyWindow.rootViewController;
SDPPMainViewController *mvc = (SDPPMainViewController *)[n viewControllers][0];
(Will need to add some casts, and should probably break out to multiple lines)
I prefer to create custom views for all my view controllers. And I define it in code by using weak references for custom views like this:
#import "MyViewController.h"
#import "MyCustomView.h"
#interface MyViewController ()
#property (nonatomic, weak) MyCustomView *customView;
#end
#implementation MyViewController
- (void) loadView
{
MyCustomView *view = [MyCustomView new];
self.view = view;
self.customView = view;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// work with custom view
self.customView.tableView.delegate = self;
....
}
#end
Is this the correct use of weak references? Will the application crash or leak, or will there be other problems?
In this case weak is fine. You assign your CustomView to self.view which is defined in the UIViewController header as
#property(nonatomic,retain) UIView *view;
so the view property has a retaining reference.
There is a possibility that your view and customView could get out of sync - so I would be tempted to define customView as readonly and implement the getter as
- (CustomView *)customView
{
return (id)self.view;
}
As you can see in the documentation of UIViewController the view controller's view property has a strong reference to the view. So the custom view object will be retained as long as you don't set the view property to something else. In short, your method works.
As you create the instance from within this controller programatically, you should use a strong reference to set the ownership clearly to this controller.
In the event that you create the view object in IB or soryboard respectively, then a weak reference to the related IBOutlet would do.