5

So I have a very basic UIPresentationController that basically display contentView centered on screen, it's size is determined by view controller preferredContentSize. (Its very much similar to regular modal view controller presented as FormSheet).

What I'm trying to achieve, is to be able to update size of this view controllers' view dynamically, by just changing it's preferredContentSize.

When I set preferredContentSize, my subclass of UIPresentationController receive information about it in:

-(void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container

But how can I from here resize the view frame with animation ? If I just call:

-(void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
{
    [UIView animateWithDuration:1.0 animations:^{
        self.presentedView.frame = [self frameOfPresentedViewInContainerView];
    } completion:nil]; 
}

Immediately gets called containerViewWillLayoutSubviews and frame is changed without animation.

-(void)containerViewWillLayoutSubviews
{
    self.presentedView.frame = [self frameOfPresentedViewInContainerView];
}

Please help me find a way, to resize it with animation. It must be possible, since it resizes with animation for example when rotation happens.

Koen.
  • 25,449
  • 7
  • 83
  • 78
DamianD
  • 101
  • 1
  • 4

3 Answers3

2

Instead of setting the frame from within preferredContentSizeDidChangeForChildContentContainer, you only need to call setNeedsLayout and layoutIfNeeded on the container view. This causes containerViewWillLayoutSubviews to be invoked, which will update the frame using your animation configuration.

Objective-C:

- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
{
    [UIView animateWithDuration:1.0 animations:^{
        [self.containerView setNeedsLayout];
        [self.containerView layoutIfNeeded];
    } completion:nil]; 
}

Swift:

override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
    super.preferredContentSizeDidChange(forChildContentContainer: container)
    
    guard let containerView = containerView else {
        return
    }
    
    UIView.animate(withDuration: 1.0, animations: {
        containerView.setNeedsLayout()
        containerView.layoutIfNeeded()
    })
}

Alternative approach

Alternatively you could not have the animate block in preferredContentSizeDidChange..., and instead place the assignment of preferredContentSize in an animate block.

This way you have more control over the animation speed of individual transitions.

Objective-C:

- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
{
    [self.containerView setNeedsLayout];
    [self.containerView layoutIfNeeded];
}

// In view controller:

[UIView animateWithDuration:0.25 animations:^{
    self.preferredContentSize = CGSizeMake(self.view.bounds.width, 500.f);
}];

Swift:

override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
    super.preferredContentSizeDidChange(forChildContentContainer: container)
    
    guard let containerView = containerView else {
        return
    }
    
    containerView.setNeedsLayout()
    containerView.layoutIfNeeded()
}

// In view controller

UIView.animate(withDuration: 0.25) {
    self.preferredContentSize = CGSize(width: self.view.width, height: 500)
}
Koen.
  • 25,449
  • 7
  • 83
  • 78
1

You have to use auto layout. Make an outlet from your containers height constraint:

@property (nonatomic, strong, readwrite) IBOutlet NSLayoutConstraint *drawerViewHeightConstraint;

then add the following code:

- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container
{
    [super preferredContentSizeDidChangeForChildContentContainer:container];
    self.drawerViewHeightConstraint.constant = container.preferredContentSize.height;

    [UIView animateWithDuration:0.25
                     animations:^{
                       [self.view layoutIfNeeded];
                     }];
}
shim
  • 9,289
  • 12
  • 69
  • 108
Nico S.
  • 3,056
  • 1
  • 30
  • 64
-2

This is not a direct answer to a question but there is another approach to create a resizable sheet, than manually changing a preferredContentSize.

In iOS 15, there is a public API to present a resizable bottom sheet (or centered sheet on iPad).

if #available(iOS 15.0, *) {
    viewController.modalPresentationStyle = .pageSheet

    if let sheet = viewController.sheetPresentationController {
        sheet.detents = [.medium(), .large()]
    }

    rootViewController.present(viewController, animated: true, completion: nil)
}

Now user can make this viewController bigger or smaller by just panning it.

This works on iPad as well.

Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70