0

I have a UIView subclass called MyView as such

@implementation MyView
{
  UIView<SomeImageProtocol> *_imageView;
}

- (instancetype) init
{
  if (self = super) {
    _imageView = ...
    [self addSubview:_imageView];
  }
}

...
- (void)layoutSubviews
{
  [super layoutSubviews];

  _imageView.frame = self.bounds;
}

..

In my MyViewController, I want to trigger some image animation, i.e shrinking in size:

-(void)viewDidLoad
{
  _dwellTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(animateView) userInfo:nil repeats:NO];
}

- (void)animateView
{
  [myView animateOnImage];
}

In MyView class, animation doesn't seem to work when I set _imageView's frames, but when I set its opacity like .alpha or .layer.cornerRadius, it works just fine. The resources I came across online suggested that _imageView has Auto Layout constraints and its frame is still controlled by I have specified in layoutSubviews. The animation seems to always end in Auto Layout.

-(void)animateOnImage {
  [UIView animateWithDuration:2 animations:^{
    [self shrinkImageView];
  }];
}

- (void)shrinkImageView {
  // Doesn't work
  _imageView.frame = CGRectMake(100, 100, 400, 500);
  // Works
  _imageView.alpha = 0.5;
}
1
  • 2
    You animate the constraints, not the frame Commented Nov 30, 2024 at 20:26

1 Answer 1

2

The problem is NOT the auto-layout constraints...

The issue is that setting the frame of a subview in [UIView animateWithDuration:...] triggers a call to layoutSubviews ... where you again set the frame of the subview.

To avoid that, we can add a CGRect property to track self.bounds and, in layoutSubviews, only update _imageView.frame if self.bounds has changed.

Example code...


MyView.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyView : UIView
-(void)animateOnImage;
@end

NS_ASSUME_NONNULL_END

MyView.m

#import "MyView.h"

@implementation MyView

{
    UIView *_imageView;
    CGRect myBounds;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _imageView = [UIView new];
        _imageView.backgroundColor = UIColor.blueColor;
        [self addSubview:_imageView];
        myBounds = CGRectZero;
    }
    return self;
}
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    // debug - log that layoutSubviews was called
    NSLog(@"layoutSubviews");
    
    // only execute the following if self.bounds has changed
    if (!CGRectEqualToRect(myBounds, self.bounds)) {
        
        // debug - log that self.bounds has changed
        NSLog(@"self.bounds changed, so set _imageView frame");
        myBounds = self.bounds;
        _imageView.frame = self.bounds;
    }
}

-(void)animateOnImage {
    [UIView animateWithDuration:2 animations:^{
        [self shrinkImageView];
    }];
}

- (void)shrinkImageView {
    _imageView.frame = CGRectMake(50, 100, 150, 200);
    _imageView.alpha = 0.5;
}


@end

AnimSubViewController.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AnimSubViewController : UIViewController

@end

NS_ASSUME_NONNULL_END

AnimSubViewController.m

#import <UIKit/UIKit.h>
#import "AnimSubViewController.h"
#import "MyView.h"

@interface AnimSubViewController ()
{
    MyView *myView;
}
@end

@implementation AnimSubViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.systemBackgroundColor;
    
    myView = [MyView new];
    myView.backgroundColor = UIColor.systemYellowColor;
    myView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:myView];
    
    UILayoutGuide *g = self.view.safeAreaLayoutGuide;
    
    [NSLayoutConstraint activateConstraints:@[
        [myView.topAnchor constraintEqualToAnchor:g.topAnchor constant:120.0],
        [myView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:80.0],
        [myView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-80.0],
        [myView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-120.0],
    ]];
    
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(animateView) userInfo:nil repeats:NO];
}

- (void)animateView
{
    [myView animateOnImage];
}

@end

demo

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! but the animation now is shrinking starting from MyView's origin x and y (0,0), then moving toward its final position, instead of shrinking from the center :(
@夢のの夢 -- I added a screen-cap of the animation to my answer. Is that not what you're getting?
unfortunately no, I see you using myView.translatesAutoresizingMaskIntoConstraints = NO; and NSLayoutConstraints in viewDidLoad I am not using any of these. Not sure if it has anything to do with it.
@夢のの夢 - no, that has nothing to do with it. If you are not using constraints, I don't now why you were talking about them in your post (and post title). Did you run my example code as-is? If not, try it. If you made changes to your code based on my answer, and it's still not working, then post your full, modified code so we can see what's happening.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.