Invalidate a timer that belongs to a UIView subclass - ios

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

Related

iOS LayoutSubviews being called multiple times when a subview resizes

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

UIView Animation Fails

So, I want to do some basic animations of labels and later views.
I have a label, I'm trying to get it to move when a view loads, so I call the following method at the end of viewDidLoad:
- (void)animateView {
NSLog(#"animateView");
[UIView animateWithDuration:20 animations:^{
// set new position of label which it will animate to
self.dcFirstRunDaysLabel.frame = CGRectMake(20,320,280,215);
}];
}
Instead of animating, the label appears in position.
I've tried every tutorial and read through the docs. I get no errors.
Any thoughts?
Cheers.
Try calling your animateView method in viewDidAppear. Because in viewDidLoad your view isn't visible yet.
viewDidLoad:
Called after the controller’s view is loaded into memory.
viewDidAppear:
Notifies the view controller that its view was added to a view hierarchy.

how to make a custom scroll container view controller?

There are two view controllers in my app, e.g, vc1 and vc2. The two view controllers are as the subviews of a scrollView, so the user can scroll the screen to switch the view. However, the simple implement has a problem: the viewWillAppear method of vc1 and vc2 is called only once. so I want to implement my scroll container view controller, which can call viewWillAppear method correctly, please tell me how to implement it.
I am not sure what you are trying to do, but I think a simple UITableView or UICollectionView may be better for you because they have datasource method that will automatically called when a view will show up in the screen. You can update your two views when you need to return a UITableViewCell or UICollectionViewCell.
I'm not sure if this will work, but I'm thinking you can check if the vc1 and vc2's frames are withing the screen's bounds in the delegate method of the scrollView.
I'm pretty sure there's a method being called every time the scrollView is being scrolled. In this method, you can check
//put this in your .h or something
BOOL vc1IsVisible = true;
//in the scrollView delegate-method that is called upon scrolling
if([self isInsideView:vc1])
{
if(!vc1IsVisible)
{
vc1IsVisible = true;
[vc1 viewDidAppear:NO]; //or whatever it is for animation
}
}
else
{
if(vc1IsVisible)
vc1IsVisible = false
//and viewDidDisappear?
}
and then create a method somewhere like this
-(BOOL)isInsideView:(UIViewController*)vc
{
//Check if vc.origin.y is greater than scrollView.size.height or something maybe?
//You can probably also try using the scrollView's contentOffset and use that
//relative to the viewController's sizes.
//if the viewControllers bounds are withing the scrolls bounds, return YES;
//else, return NO;
}
Sorry I can't really test anything just now. Maybe I'll make something and update the answer later if you haven't figured it out. And you need to do it with both. I'm sure you can figure out a better way to include both in one method with this, or even with one variable.
Since you are using ViewController by adding it subview of scrollview, by adding ViewController this way viewDidLoad, viewWillAppear, viewDidAppear will be called only once, I mean there is no use of viewWillAppear here as such, rather if you want to update anything in the added ViewController you should create a public class in ViewController and call it when you need an update..

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)

dismissModalViewControllerAnimated resets contentOffset

I have a problem with my table view. When dismissing a modal view controller presented on top of it, it always scrolling to the top . I have tried observing the changes to contentOffset using KVO, but the one that messes my view goes behind it.
From the UITableViewController, when user finishes his task in the modal dialog, self.tableView.contentOffset is , I call:
[self dismissModalViewControllerAnimated:YES]
Subsequently, when the viewWillAppear:(BOOL)animated is called, the self.tableView.contentOffset is already set to 0,0.
Is this supposed to be happening? I am able to work around the issue by remembering the scroll position before presenting the modal view and restore it back in viewWillAppear after dismissing the modal view. But it seems wrong. Am I missing something?
I have found similar problem described in Dismiss modal view changes underlying UIScrollView.
It looks like this is default behavior of UITableViewController. I tested it in very simple app and It worked exactly as you said. If you don't like it, use UIViewController instead.
Here is how I work around this problem, so that the table view maintains the original scroll position. In my subclass of UITableViewController I have added:
#property (assign) CGPoint lastScrollPosition;
Then in the implementation, I have overridden the following:
- (void)viewWillAppear:(BOOL)animated
{
self.tableView.contentOffset = self.lastScrollPosition;
}
- (void)dismissModalViewControllerAnimated:(BOOL)animated
{
self.lastScrollPosition = self.tableView.contentOffset;
[super dismissModalViewControllerAnimated:animated];
}
If you want your table to initially appear scrolled to non-zero position, as I did, don't forget to initialize the lastScrollPosition in your viewDidLoad.

Resources