iOS LayoutSubviews being called multiple times when a subview resizes - ios

In my program I have started doing all initialization of objects in the init method without setting a frame and then in layoutSubviews I set the frames for these objects to make sure that they are properly set.
Firstly is this proper practice to initialize all objects in the init function without a set frame and then in layoutSubviews set each of their frames. The reason for my concern is that it is called quite often.
So I have a UIView subclass where I call these methods in the layoutSubviews
- (void)layoutSubviews
{
[super layoutSubviews];
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
}
This currently works fine and all the objects are set correctly, but the problem is in my colorPicker class when the user touches the screen I adjust the frame of the colorPicker and by doing so this method gets called from the subview colorPicker and it readjusts a frame that it shouldn't since it has been modified in the subview. The subview causes the superviews layoutSubview to be called and this is not what I need.
My question is, is there a way to stop this behavior from happening or should I not use layoutSubviews to set frames because I was told this is a better way of making views programmatically?

Off the top of my head, there's two ways to fix this. You can either move this code to where the view is initialized, either in init, initWithFrame:, or initWithCoder:, depending on which you're using. It's good practice to make a separate method to initialize everything for your view, and call it from all the init methods to make sure it's always initialized correctly no matter how you instantiate the view.
Alternatively, if you want to keep your code in layoutSubviews, in your #interface add a boolean to flag that the frames were already set
#interface MyView : UIView
#property (nonatomic, assign) BOOL framesAreSet;
#end
Then when you set your frames, check if you already did
- (void)layoutSubviews
{
[super layoutSubviews];
if (!_framesAreSet)
{
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
_framesAreSet = YES;
}
}

Your issue is likely that your colorPicker class is handling the touch methods to adjust its own frame. Instead, you should handle the touch methods in colorPicker's superview class, and have that superview class adjust colorPicker's frame in response to the touches.
Also, I would recommend doing all UI initialization in initWithFrame:, not init. The reason is because calling init on UIView ends up calling initWithFrame:.

Related

Where to setup a UIView subclass' subviews?

I have a simple UIView subclass where I place a UILabel within, in order to give such UIView a certain frame, background color and text format for its label from a UIViewController.
I'm reading the View Programming Guide for iOS but there are some things that I'm not fully understanding... when your view has only system standard subviews, such my UILabel, should I override the drawRect: method? Or is that only intended for Core Animation staff? If I shouldn't setup standard subviews within such method, what is the correct place to do so? Should I then override the init method?
Thanks in advance
No, you should not override the drawRect for initializing the sub views because it causes a performance hit. That should be done either in the init, initWithFrame, or initWithCoder methods. For example, this is how you do it using the initWithFrame method
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//initialize sub views
}
return self;

Subview acessing superview methods

How can a subview access the methods from its superview? I have a button, and when pressed I would like the button to call a method from its superview, but I do not know how.
suppose your super View class name is
MainView.h
sub View Name
SubView.h
So In sub class you can do
MainView *myMainView = (Mainview *)[self superview];
[myMainView someMethod];
Make sure someMethod is public Method.
Other way you could have reference to all the view is set a tag
For example
myMainView.tag = 100; or self.tag = 100;
In the subview you could do
MainView *myMainView = (Mainview *)[self viewWithTag:100];
[myMainView someMethod];
a weird construct but just call the method:
inside a view you have have self.superview
since self.superview is a UIView*, the compiler will claim it is invalid to call method XYZ on it. Cast it to id or to your class name to use it
e.g.
[(id)self.superview myMethod];
or even
id myValue = [(id)self.superview myMethod:param1];
One method is to use delegates.
#protocol ButtonHandlingDelegate <NSObject>
- (void) subviewButtonWasPressed;
#end
In your Subview add this:
#property (nonatomic, weak) id selectionDelegate;
When subview is created, set delegate to superview.
Define Superview as delegate in .h file
#interface SuperView : UIView <ButtonHandlingDelegate>
in Superview .m file
- (void) subviewButtonWasPressed{
// Do somethign about it
}
All of the answers listed are hacky and bad style. You should be using delegation through the subview's ViewController. What you will want to do is create a protocol for your subview with a void method called something like specialButtonPressedOnView:(SUBVIEWCLASS *)view. Then in the subview's view controller you should make yourself the delegate for this protocol and respond to the delegate method from the VC's context.
Using self.superview is a bad idea because you cannot guarantee what your superview is going to be and generally blindly calling method on objects (ESPECIALLY those cast to id) is a really bad idea.
The other answer that suggested having the superview implement the subview's protocol is also not good style because you're creating a dependency between your views. If you were thinking of going down this path your subview should probably just be a subclass of the superview.
One of the core parts of the MVC design pattern is making sure that your views are reusable and totally independent. They are just structures that you can inject your content into, to which they will respond with pretty UI, and they don't need to know anything about what's being passed to them and don't have to ask other views for help. So either using delegation through the subview's ViewController or subclassing is probably the best direction. Both methods preserve the MVC pattern and are much safer than the other suggestions.

When will (or won't) updateViewConstraints be called on my view controller for me by the framework?

I have a view controller and I want to craete and set my view constraints in updateViewConstraints.
I have a break point in that method and it's never getting called.
Why might it not be getting called?
When will the framework want to call this method on my view controller?
updateViewConstraints is called by viewWillLayoutSubviews (via your view's layoutSubviews method along the way). As the name suggests, this is called whenever your view controller needs to update its layout.
If you finding that updateViewConstraints is never being called then you should make sure that you are calling super your view controller's methods where required.
Also, it sounds as if you are creating your constraints inside updateViewConstraints? That may also be the cause of your problem. You should be updating your constraints in that method, not creating them (in the same way that in layoutSubviews you position your views, but don't instantiate them).
You are in ViewController and you want the system to call updateViewConstraints, then you have to call this
[self.view setNeedsUpdateConstraints];
Your UIView must implement + (BOOL)requiresConstraintBasedLayout and return YES. In that case updateViewConstraints will be initially called to setup constraints.
Just a guess and not 100% certain:
updateViewConstraints won't be called if your view controllers root view does not require auto layout. So you have to at least add one constraint to your root view (somewhere in loadview or viewDidLoad) or set
self.view.translatesAutoresizingMaskIntoConstraints = NO;
This should trigger your break point.
You can add constraints in updateViewConstraints if you add a flag to indicate whether constraints have been added. See also my gist
#implementation ViewController {
bool _constraintsAdded;
}
- (void)updateViewConstraints {
if (!_constraintsAdded) {
_constraintsAdded = true;
// TODO: add constraints to self.view's subviews
}
NSLog(#"update constraint.");
// TODO: update constraints needed
[super updateViewConstraints]; // must be called finally.
}
#end
You should call setNeedsUpdateConstraints as the Apple documentaion says:
To schedule a change, call setNeedsUpdateConstraints on the view. The system then calls your implementation of updateViewConstraints before the layout occurs.
(Apple reference)

UIViewController viewDidLoad incorrect width/height

Everybody knows that you can't trust the frame size on a UIViewController init/viewDidLoad method; this:
- (void)viewDidLoad: {
NSLog(#"%d", self.view.frame.size.width);
}
will print wrong sizes in many occasions (in particular it's pretty much broken in landscape mode)
This will actually return always corrected results so it's good to layout the subviews:
- (void)viewWillAppear: {
NSLog(#"%d", self.view.frame.size.width);
}
The problem is that viewWillAppears gets called every time the view appears, so it's not suitable to alloc or add subviews. So you end up declaring every single view in the interface and you end up with huge header files that I don't like at all since most of the items don't need any more manipulations after the initial setup.
So question one is: Is there a better way to handle subviews positioning?
Question two is very related, let's say I have a subclass of UIView including various others subviews. I declare it inside my interface, and i alloc/init it in my init/viewDidLoad method.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
...
menu = [[SNKSlidingMenu alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
...
}
As we already know we now need to reposition it in viewWillAppear to get a more accurate reading
- (void)viewWillAppear:(BOOL)animated{
....
menu.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
....
}
The problem is that of course all the subviews needs to be repositioned as well. This is done by the layoutSubviews function that get called automatically, but we got the same problem: All the subviews need to be declared inside the interface of the SNKSlidingMenu class.. Is there a way around this?
Thanks.
If you are targetting iOS 5.0 or better you can use viewWillLayoutSubviews and viewDidLayoutSubviews to make changes.
As for your second question, if you need access to an instance variable in other method than init, you need to keep it around, I don't see a problem with it.
You can, however, try to use Auto Layouts and set up rules between the subviews so it's automatically laid out for you without the need to keep a reference.
viewDidLoad only gets called when your view is created, but lots of things can affect the frame's size, and it doesn't get called again when frame changes.
Instead:
create subviews in viewDidLoad
set their sizes in
viewWillLayoutSubviews.
See some additional discussion here for handling rotation: https://stackoverflow.com/a/16421170/1445366
viewWillLayoutSubviews and viewDidLayoutSubviews can resolve this problem.
But the two methed would be performed more times.
this is my code to get correct self.view.frame.
- (void)viewDidLoad {
[super viewDidLoad];
...
dispatch_async(dispatch_get_main_queue(), ^{
// init you view and set it`s frame. this can get correct frame.
...
}
...
}
This saved my life more than once (Swift 4):
override func viewDidLoad() {
super.viewDidLoad()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
Basically this forces the viewcontroller to correctly layout it's view and from there you can get the correct frames for all your subviews. This particularly helps when doing transition animations and your view controllers are using autolayout and interface builder.
From what I've noticed it looks like the initial frames are set to whatever your interface builder's default size class is set to. I normally edit using the iPhone XS size class so in viewDidLoad it seems that the view's width is always 375 regardless whether you are using an iPhone XR or not. This corrects itself before viewWillAppear though.
The above code will correct this issue and allow you to get the correct frames for your view / subviews before the view controller is rendered to the screen.

Invalidate a timer that belongs to a UIView subclass

I have a repeating timer that belongs to a UIView subclass.
The class has a nib that loads it and I'm using ARC.
I'd like to invalidate the timer when the UIView is either...
Removed from its superview
The ViewController that contains its superView is popped off the stack.
I can't seem to find a method like viewDidDisappear on UIView.
Is there any other way to intercept this?
At the moment, after the ViewController is popped the timer keeps firing and creating NSLog outputs.
For the view controller being popped: just use viewDidDisappear or similar. There's also UINavigationControllerDelegate that may be useful.
For the view itself: have you tried using willMoveToSuperview: method in UIView? I haven't verified this, but in theory the view will move to superview nil when it is removed from its superview.
So try the following in your view:
- (void)willMoveToSuperview:(UIView *)superview {
if (!superview) {
// cancel timers
}
}
There's also a willRemoveSubview: method, but that would get called on the superview rather than the view being removed.
Have you tried invalidating it in the dealloc

Resources