iPhone UIView - 调整框架以适应子视图

时间:2010-08-31 01:11:25

标签: iphone uiview resize

在添加子视图后,是否应该有一种方法来调整UIView的帧大小,以便框架是包含所有子视图所需的大小?如果动态添加子视图,您如何确定容器视图的框架需要的大小?这不起作用:

[myView sizeToFit];

11 个答案:

答案 0 :(得分:43)

您还可以添加以下代码来计算子视图位置。

[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

答案 1 :(得分:24)

我需要使子视图具有负原点,并且CGRectUnion是ObjC这样做的方式,老实说,正如评论中提到的那样。首先,让我们看看它是如何工作的:

如下所示,我们假设一些子视图位于外部,因此我们需要做两件事来使其看起来不错,而不会影响子视图的定位:

  1. 将视图框移动到最左上方位置
  2. 将子视图移动到相反的方向以取消效果。
  3. 一张图片胜过千言万语。

    demonstration

    代码值十亿字。这是解决方案:

    @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
    

    我觉得用Swift 2.0编写会很有趣..我是对的!

    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
        }
    }
    

    游乐场证明:

    请注意,visualAidView不会移动,并可帮助您查看superview在维护子视图位置时如何调整大小。

    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

答案 2 :(得分:18)

看起来Kenny 's answer以上指向referenced question中的正确解决方案,但可能已经取消了错误的概念。 UIView class reference肯定会建议一个使sizeToFit与您的自定义视图相关的系统。

覆盖sizeThatFits,而不是sizeToFit

您的自定义UIView需要覆盖sizeThatFits以返回“适合接收方子视图的新大小”,但您希望计算此值。您甚至可以使用another answer中的数学来确定新的大小(但不重新创建内置的sizeToFit系统)。

sizeThatFits返回与其状态相关的数字后,在您的自定义视图上调用sizeToFit将开始导致预期的调整大小。

sizeThatFits如何运作

如果没有覆盖,sizeThatFits只会返回传入的尺寸参数(默认为self.bounds.size来自sizeToFit的来电。虽然我只有couple {{3在这个问题上,看来传入的大小不应被视为严格要求。

  

[sizeThatFits]返回适合传递给它的约束的控件的“最合适”大小。方法可以(强调他们的)决定忽略约束,如果它们无法满足。

答案 3 :(得分:16)

查看Having trouble getting UIView sizeToFit to do anything meaningful

要点是sizeToFit是要覆盖的子类,并且在UIView中不做任何事情。它会为UILabel执行操作,因为它会覆盖由sizeThatFits:

调用的sizeToFit

答案 4 :(得分:2)

更新了@Mazyod对Swift 3.0的回答,就像一个魅力!

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
    }
}

答案 5 :(得分:1)

旧问题,但您也可以使用递归函数执行此操作。

无论有多少子视图和子视图,您都可能需要一个始终有效的解决方案......

更新:上一段代码只有一个getter函数,现在也是一个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)
        }
    }
}

答案 6 :(得分:1)

尽管已经有很多答案,但没有一个适合我的用例。如果使用autoresizingMasks并希望调整视图的大小并移动子视图,以使矩形的大小与子视图匹配。

public extension UIView {

    func resizeToFitSubviews() {
        var x = width
        var y = height
        var rect = CGRect.zero
        subviews.forEach { subview in
            rect = rect.union(subview.frame)
            x = subview.frame.x < x ? subview.frame.x : x
            y = subview.frame.y < y ? subview.frame.y : y
        }
        var masks = [UIView.AutoresizingMask]()
        subviews.forEach { (subview: UIView) in
            masks.add(subview.autoresizingMask)
            subview.autoresizingMask = []
            subview.frame = subview.frame.offsetBy(dx: -x, dy: -y)
        }
        rect.size.width -= x
        rect.size.height -= y
        frame.size = rect.size
        subviews.enumerated().forEach { index, subview in
            subview.autoresizingMask = masks[index]
        }
    }
}

答案 7 :(得分:0)

动态添加所有这些子视图的模块必须知道放置它们的位置(因此它们正确关联,或者它们不重叠等)一旦你知道了,加上当前视图的大小,再加上子视图的大小,您需要确定是否需要修改封闭视图。

答案 8 :(得分:0)

这是一个接受答案的快速版本,也是一个小改动,而不是扩展它,这个方法将视图作为一个变量并返回它。

func resizeToFitSubviews(#view: UIView) -> UIView {
    var width: CGFloat = 0
    var height: CGFloat = 0

    for someView in view.subviews {
        var aView = someView as UIView
        var newWidth = aView.frame.origin.x + aView.frame.width
        var newHeight = aView.frame.origin.y + aView.frame.height
        width = max(width, newWidth)
        height = max(height, newHeight)
    }

    view.frame = CGRect(x: view.frame.origin.x, y: view.frame.origin.y, width: width, height: height)
    return view
}

继承人的扩展版本:

/// Extension, resizes this view so it fits the largest subview
func resizeToFitSubviews() {
    var width: CGFloat = 0
    var height: CGFloat = 0
    for someView in self.subviews {
        var aView = someView as! UIView
        var newWidth = aView.frame.origin.x + aView.frame.width
        var newHeight = aView.frame.origin.y + aView.frame.height
        width = max(width, newWidth)
        height = max(height, newHeight)
    }

    frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: width, height: height)
}

答案 9 :(得分:0)

当您创建自己的UIView类时,请考虑在子类中重写IntrinsicContentSize。 OS调用此属性以了解建议的视图大小。我将为C#(Xamarin)放入代码段,但想法是相同的:

[Register("MyView")]
public class MyView : UIView
{
    public MyView()
    {
        Initialize();
    }

    public MyView(RectangleF bounds) : base(bounds)
    {
        Initialize();
    }

    Initialize()
    {
        // add your subviews here.
    }

    public override CGSize IntrinsicContentSize
    {
        get
        {
            return new CGSize(/*The width as per your design*/,/*The height as per your design*/);
        }
    }
}

此返回的大小完全取决于您的设计。例如,如果您只是添加了标签,则width和height就是该标签的宽度和高度。如果您的视图更为复杂,则需要返回适合所有子视图的大小。

答案 10 :(得分:-4)

[myView sizeToFit];

应该工作,你为什么不在前后检查CGRect?