How to animate NSLayoutConstraint without IBOutlet - ios

I have a View Controller containing a view (of another controller) and a tab bar at the bottom. I want to add an iAD banner such that when an ad is loaded and unloaded, the contained view is resized accordingly.
/* AutoLayout */
/* pin Left of child to left of parent */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
/* pin Right of child to right of parent */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
/* pin top of child to bottom of nav bar(or status bar if no nav bar) */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
/* pin Top of tab bar to bottom of child view */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:child.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
/*
* adConstraint is an instance variable to hold the constraint I want to animate
* Note that it is not an IBOutlet
*/
adConstraint = [NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
[self.view addConstraint:adConstraint];
In my delegate methods i have the following
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
UIView animateWithDuration:1 animations:^{
adConstraint.constant = -banner.frame.size.height;
[self.view layoutIfNeeded];
}];
}
and
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
UIView animateWithDuration:1 animations:^{
adConstraint.constant = 0;
[self.view layoutIfNeeded];
}];
}
I seriously need help with this! Is it even possible to achieve this without Storyboard and the IBOutlet or am I doing something (terribly) wrong?
Please Help?!
*********************** EDIT: ************************
I've noticed a problem; The ad shows up but bannerDidLoadAd is not called. I know this because I added log statements in it. Does anyone know why this might be happening????

Try calling
[self.view setNeedsUpdateConstraints];
before
[self.view layoutIfNeeded];
It should work!
[UIView animateWithDuration:1 animations:^{
adConstraint.constant = 0;
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}];

Related

How to update web view width after device rotation in objective c?

When I first load the web view its frame is correct but when I rotate the device, its width is not update.
For instance, if the view was portrait and I rotate it in landscape the web view frame is not cover the whole view.
Loading web view
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadWebView];
}
-(void) loadWebView {
UIWebView *webView;
webView = [[UIWebView alloc] initWithFrame: self.view.frame];
NSString *htmlString = [NSString stringWithFormat:#"%#%#%#",html_header_with_files,DetailsHtml,HTML_FOOTER];
[webView loadHTMLString:htmlString baseURL:nil];
[self.view addSubview:webView];
}
First try:
I added a notification to realize the rotation
- (void) orientationChanged:(NSNotification *)note
{
[self.view layoutIfNeeded];
}
Above code did not solve the problem.
Second try
- (void) orientationChanged:(NSNotification *)note
{
[self loadWebView];
}
Above code did not solve the problem.
The view has to be added with auto layout so that it gets laid out properly in all devices with both orientation.
You need to create an extension of UIView and add the below method to make it a reusable code. You can also add the method in the same class if you are not going to use this method anywhere else.
- (void)addSubView:(UIView *)subView belowView:(UIView *)belowView inSuperView:(UIView *)superView {
[superView addSubview:subView];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
if (nil == belowView) {
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
} else {
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:belowView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
}
}
Then you need to call the above method like
[self addSubView:webView belowView:nil inSuperView:self.view];
Note:
To know more about auto-layout you can follow the tutorial
https://www.raywenderlich.com/443-auto-layout-tutorial-in-ios-11-getting-started
Also its better to start writing your app with swift rather than continuing objective-c even if some of your codebase is already in objective-c. A tutorial to make obj-c and swift inter operability can be seen in below link
https://medium.com/ios-os-x-development/swift-and-objective-c-interoperability-2add8e6d6887

Slide In A UIView by updating constraints

I am trying to animate a UIView by updating its constraints. I want to initially place the view at the bottom of the screen (off screen) and then set the top constraint to 0 so that the animation shows the view moving upwards.
I have tried the following with no luck at all, the animation is incorrect.
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
//CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
self.viewSearchWrapper.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.viewSearchWrapper];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:0.f];
// Leading
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *bottom = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *top = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:screenHeight];
[self.view addConstraint:trailing];
[self.view addConstraint:bottom];
[self.view addConstraint:leading];
[self.view addConstraint:top];
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add layoutIfNeeded to first set the initial constraint to viewSearchWrapper.
// .... keep the code assigning the constraints as it is.
[self.view layoutIfNeeded]; // This will set the initial frame of viewSearchWrapper
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add [top setConstant:0.f] inside animateWithDuration as shown below:
[UIView animateWithDuration:1 animations:^{
[top setConstant:0.f];
[self.view layoutIfNeeded];
}];

Subview visible but frame outside superview frame

I have a parent VC that loads a child VC inside it. Code in parent VC is as follows:
self.draggerViewController = [[DraggerViewController alloc] initWithSuperView:self.view];
And the code of the child VC is as follows:
- (instancetype)initWithSuperView:(UIView*)superView {
self = [super init];
if (self) {
self.superView = superView;
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
[superView addSubview:self.view];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:superView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:superView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]];
self.constraintDraggerAttributeBottom = [NSLayoutConstraint constraintWithItem:superView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[superView addConstraint:self.constraintDraggerAttributeBottom];
}
return self;
}
With this code, the subview is visible on its parent view and constraints are applied correctly so that it is placed at the bottom of the parent view with leading and trailing being 0. BUT, the real frame of the view is outside the parent view.
That is, I can see the subview at the bottom of the parent view but the frame of this subview is frame = (0 -315; 768 35).
If I set 'clipToBounds' to 'YES', the view is placed on the real frame but now constraints are not applied correctly.
How can I place this child VC inside the parent VC's view at the position I want using constraints?
Thank you!
OK, got it...
I was forgetting the height constraint of the view... What a shame...
[superView addConstraint:[NSLayoutConstraint constraintWithItem:superView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
Thank you all!
Try this :
superView.insertSubview(self.view, at: self. superView.subviews.count)

Detect UIView animation and copy it

I have two views. Lets call them view1 and view2. They are at the same level in the views hierarchy. I want view2 to always follow view1 (both in position and size). I added some constraints to do that and it works fine. The problem is with the animations. Whenever I animate view1 I want view2 to do the same thing. Note that only view1 is visible to the 'outside world', and only view1 knows about view2 so I cannot just add view2 in the animations block. Can somehow view2 copy the animations of view1?
EDIT:
view1 is animated either by changing the frame or by changing constraints and view2 should work in both cases.
- (void)addView2 {
[self.superview insertSubview:self.view2 belowSubview:self];
self.view2.translatesAutoresizingMaskIntoConstraints = NO;
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
}
This is the relevant code from view1.
Animations:
[UIView animateWithDuration:kAnimationDuration animations:^{
view1.frame = some other frame;
}];
or
[UIView animateWithDuration:kAnimationDuration animations:^{
[view1 layoutIfNeeded]
}];
I dont know your animation code, I guess you are using autolayout so seems your animation act to the frame and not to the constraints involved. This can cause (to one or both view during animation) an ugly size distortion. Check your code and correct all animations by using constraints.
A simple example:
UIView.animateWithDuration(0.5, delay: 1.0, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in
self.view1LeadingConstraint.constant = 30
}, completion: { (finished: Bool) -> Void in
})
Write [self.view layoutIfNeeded]; before animating any view :
for example,
[self.view layoutIfNeeded];
[UIView animateWithDuration:kAnimationDuration animations:^{
view1.frame = some other frame;
}];
You need to call layoutIfNeeded before animation it if you use autolayout and animating it by manipulating frames.
If it is not work then write layoutIfNeeded for other both view too and then animate
Hope this will help

Custom UIView (with xib) autolayout width and pushing viewcontroller from delegate

I have problem with setting constraints to custom UIView and pushing new ViewController by delegate when I tap the view. I tried to find solution for these problems but couldn't find one that answers to these questions.
Autolayout problem
I have custom xib-file which has been set Size: Freeform, Width: 600 and Height: 25, it also includes one label and one button with constraints in this view. I have added this view successfully below navigation bar where I want it. Problem is, that it don't make anything to fit it's width equally with navigation bar / window size (I have tried multiple choices eg. making new frame for view that is width of window / navigation bar). It only appears to have static 600 width all the time whatever I try.
First two constraints are working, it appears 25 points below navigation bar and it centers it. But last one won't make anything.
How should I do this properly? So far have this:
[self.subView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.subView];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.navBar
attribute:NSLayoutAttributeBottom
multiplier:1
constant:25.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0]];
Should I do something more with xib-file that it will make this width to fit it's parent view? I have also implemented initWithFrame, initWithCoder and intrinsicContentSize to my custom view.
Solution
I ended up to make containerView for my subView and center it vertically and horizontally and found right constraint for width. I also forgot to update my subView's view frames to match navigation bar width. Here is what I ended up to (if there is better way to do this, I take critic with pleasure):
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 62, self.navBar.frame.size.width, 25)];
[self.view addSubview:self.containerView];
self.subView = [[SubView alloc]init];
[self.subView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.containerView addSubview:self.subView];
self.subView.view.frame = CGRectMake(0, 0, self.containerView.frame.size.width, self.containerView.frame.size.height);
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.containerView addConstraint:
[NSLayoutConstraint constraintWithItem:self.subView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0]];
Delegate problem (solved)
For answer to this problem: check MisterGreen's answer below.
Another problem occured when I made UITapGestureRecognizer with delegate in my custom view. What I want is when I tap the view, it opens another ViewController. The delegate function is like this where I implement my custom view:
-(void)pushViewControllerUsingDelegate
{
NSLog(#"DELEGATE WAS : %#", self.subView.delegate);
[self pushViewController:self.anotherViewController animated:YES];
}
Now it gives exception when I tap the view:
DELEGATE WAS : <MasterViewController: 0x7fc96132e7d0> <-- Delegate is OK
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AnotherViewController 0x7fc961248230> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key subViewButton.'
What this actually means? I have this subViewButton IBOutlet with weak property, does it have something to do with this? Or is there another way to make this happen?
Tutorial which I followed: https://www.youtube.com/watch?v=TfKv1MYxnA4
Because there is not enough data to be exactly sure what is the problem you encountered, i have just created a code snippet that is working and doing exactly what you are trying to get.
About the constraints i think the problem is the hight constraint that is missing(unless you determined it elsewhere),
try to remember that when you add constraints provide enough data to the compiler to understand how to resize and position your subview according to it's superview, in your case it didn't know what is the hight cause you didn't supply nor bottom or hight constraint to determine it.
About the delegate method you didn't supply enough data to exactly determine what is the problem, so i've written something that i think is doing what you are trying to get.
This code snippet is tested and working:
The subview:
View.h
#protocol viewManager <NSObject>
#optional
- (void)subviewWasTapped;
#end
#interface View : UIView
#property (nonatomic, strong) id<viewManager>delegate;
#end
View.m
#implementation View
- (void)awakeFromNib{
[super awakeFromNib];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(viewWasTapped:)];
[self addGestureRecognizer:tap];
}
- (void)viewWasTapped:(NSNotification *)notification
{
[self sendViewWasTappedToDelegate];
}
- (void)sendViewWasTappedToDelegate
{
#synchronized(_delegate)
{
if([_delegate respondsToSelector:#selector(subviewWasTapped)])
{
[_delegate subviewWasTapped];
}
}
}
#end
FirstViewController:
#interface ViewController () <viewManager>
#property (nonatomic, strong) View *subview;
#end
#implementation ViewController
#synthesize subview;
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil];
subview = [subviewArray objectAtIndex:0];
[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:subview];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0.0]];
// Height constraint to determine the
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:25.0]];
[self.view layoutIfNeeded];
[subview setDelegate:self];
}
#pragma mark - viewManager delegate method
- (void)subviewWasTapped{
SecondeViewController *secondeVC = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondeViewController"];
[self.navigationController pushViewController:secondeVC animated:YES];
}

Resources