iPhone UIView - Resize Frame to Fit Subviews

前端 未结 11 1475
时光取名叫无心
时光取名叫无心 2020-12-04 17:51

Shouldn\'t there be a way to resize the frame of a UIView after you\'ve added subviews so that the frame is the size needed to enclose all the subviews? If your subviews are

相关标签:
11条回答
  • 2020-12-04 18:34

    You could also add the following code to calculate subviews position.

    [myView resizeToFitSubviews]
    

    UIViewUtils.h

    #import <UIKit/UIKit.h>
    
    @interface UIView (UIView_Expanded)
    
    -(void)resizeToFitSubviews;
    
    @end
    

    UIViewUtils.m

    #import "UIViewUtils.h"
    
    @implementation UIView (UIView_Expanded)
    
    -(void)resizeToFitSubviews
    {
        float w = 0;
        float h = 0;
    
        for (UIView *v in [self subviews]) {
            float fw = v.frame.origin.x + v.frame.size.width;
            float fh = v.frame.origin.y + v.frame.size.height;
            w = MAX(fw, w);
            h = MAX(fh, h);
        }
        [self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, w, h)];
    }
    
    @end
    
    0 讨论(0)
  • 2020-12-04 18:35

    Updated @Mazyod's answer to Swift 3.0, worked like a charm!

    extension UIView {
    
    func resizeToFitSubviews() {
    
        let subviewsRect = subviews.reduce(CGRect.zero) {
            $0.union($1.frame)
        }
    
        let fix = subviewsRect.origin
        subviews.forEach {
            $0.frame.offsetBy(dx: -fix.x, dy: -fix.y)
            }
    
            frame.offsetBy(dx: fix.x, dy: fix.y)
            frame.size = subviewsRect.size
        }
    }
    
    0 讨论(0)
  • 2020-12-04 18:36

    It looks like Kenny 's answer above points to the right solution in the referenced question, but may have taken away the wrong concept. The UIView class reference definitely suggests a system for making sizeToFit relevant to your custom views.

    Override sizeThatFits, not sizeToFit

    Your custom UIViews needs to override sizeThatFits to return "a new size that fits the receiver's subviews", however you wish to calculate this. You could even use the math from another answer to determine your new size (but without recreating the built-in sizeToFit system).

    After sizeThatFits returns numbers relevant to its state, calls to sizeToFit on your custom views will start causing the expected resizes.

    How sizeThatFits works

    Without an override, sizeThatFits simply returns the passed-in size parameter (defaulted to self.bounds.size for calls from sizeToFit. While I only have a couple sources on the issue, it appears that the passed-in size is not to be seen as a strict demand.

    [sizeThatFits] returns the "most appropriate" size for the control that fits the constraints passed to it. The method can (emphasis theirs) decide to ignore the constraints if they cannot be met.

    0 讨论(0)
  • 2020-12-04 18:37

    I needed to fit subviews had a negative origin point, and CGRectUnion is the ObjC way of doing it, honestly, as someone mentioned in the comments. First, let's see how it works:

    As you can see below, we assume some subviews are lying outside, so we need to do two things to make this look good, without affecting the positioning of the subviews:

    1. Move the frame of the view to the top left most position
    2. Move the subviews the opposite direction to negate the effect.

    A picture is worth a thousand words.

    demonstration

    Code is worth a billion words. Here is the solution:

    @interface UIView (UIView_Expanded)
    
    - (void)resizeToFitSubviews;
    
    @end
    
    @implementation UIView (UIView_Expanded)
    
    - (void)resizeToFitSubviews
    {
        // 1 - calculate size
        CGRect r = CGRectZero;
        for (UIView *v in [self subviews])
        {
            r = CGRectUnion(r, v.frame);
        }
    
        // 2 - move all subviews inside
        CGPoint fix = r.origin;
        for (UIView *v in [self subviews])
        {
            v.frame = CGRectOffset(v.frame, -fix.x, -fix.y);
        }
    
        // 3 - move frame to negate the previous movement
        CGRect newFrame = CGRectOffset(self.frame, fix.x, fix.y);
        newFrame.size = r.size;
    
        [self setFrame:newFrame];
    }
    
    @end
    

    I thought it would be fun to write in Swift 2.0 .. I was right!

    extension UIView {
    
        func resizeToFitSubviews() {
    
            let subviewsRect = subviews.reduce(CGRect.zero) {
                $0.union($1.frame)
            }
    
            let fix = subviewsRect.origin
            subviews.forEach {
                $0.frame.offsetInPlace(dx: -fix.x, dy: -fix.y)
            }
    
            frame.offsetInPlace(dx: fix.x, dy: fix.y)
            frame.size = subviewsRect.size
        }
    }
    

    And the playground proof:

    Notice the visualAidView doesn't move, and helps you see how the superview resizes while maintaining the positions of the subviews.

    let canvas = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
    canvas.backgroundColor = UIColor.whiteColor()
    
    let visualAidView = UIView(frame: CGRect(x: 5, y: 5, width: 70, height: 70))
    visualAidView.backgroundColor = UIColor(white: 0.8, alpha: 1)
    canvas.addSubview(visualAidView)
    
    let superview = UIView(frame: CGRect(x: 15, y: 5, width: 50, height: 50))
    superview.backgroundColor = UIColor.purpleColor()
    superview.clipsToBounds = false
    canvas.addSubview(superview)
    
    [
        {
            let view = UIView(frame: CGRect(x: -10, y: 0, width: 15, height: 15))
            view.backgroundColor = UIColor.greenColor()
            return view
        }(),
        {
            let view = UIView(frame: CGRect(x: -10, y: 40, width: 35, height: 15))
            view.backgroundColor = UIColor.cyanColor()
            return view
        }(),
        {
            let view = UIView(frame: CGRect(x: 45, y: 40, width: 15, height: 30))
            view.backgroundColor = UIColor.redColor()
            return view
        }(),
    
    ].forEach { superview.addSubview($0) }
    

    playground image

    0 讨论(0)
  • 2020-12-04 18:40

    Old question but you could also do this with a recursive function.

    You might want a solution that always works no matter how many subviews and subsubviews,...

    Update : Previous piece of code only had a getter function, now also a setter.

    extension UIView {
    
        func setCGRectUnionWithSubviews() {
            frame = getCGRectUnionWithNestedSubviews(subviews: subviews, frame: frame)
            fixPositionOfSubviews(subviews, frame: frame)
        }
    
        func getCGRectUnionWithSubviews() -> CGRect {
            return getCGRectUnionWithNestedSubviews(subviews: subviews, frame: frame)
        }
    
        private func getCGRectUnionWithNestedSubviews(subviews subviews_I: [UIView], frame frame_I: CGRect) -> CGRect {
    
            var rectUnion : CGRect = frame_I
            for subview in subviews_I {
                rectUnion = CGRectUnion(rectUnion, getCGRectUnionWithNestedSubviews(subviews: subview.subviews, frame: subview.frame))
            }
            return rectUnion
        }
    
        private func fixPositionOfSubviews(subviews: [UIView], frame frame_I: CGRect) {
    
            let frameFix : CGPoint = frame_I.origin
            for subview in subviews {
                subview.frame = CGRectOffset(subview.frame, -frameFix.x, -frameFix.y)
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题