簡體   English   中英

iPhone UIView - 調整框架以適應子視圖

[英]iPhone UIView - Resize Frame to Fit Subviews

添加子視圖后,是否應該有一種方法來調整 UIView 的框架大小,以便框架是包含所有子視圖所需的大小? 如果您的子視圖是動態添加的,您如何確定容器視圖的框架需要多大? 這不起作用:

[myView sizeToFit];

您還可以添加以下代碼來計算子視圖位置。

[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

我需要讓子視圖有一個負的原點,而CGRectUnion是 ObjC 的方法,老實說,正如評論中提到的那樣。 首先,讓我們看看它是如何工作的:

正如你在下面看到的,我們假設一些子視圖在外面,所以我們需要做兩件事來讓它看起來很好,而不影響子視圖的定位:

  1. 將視圖的框架移動到最左上角的位置
  2. 將子視圖向相反方向移動以消除效果。

一張圖片勝過千言萬語。

示范

代碼價值十億字。 這是解決方案:

@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如何在保持子視圖位置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) }

游樂場圖片

看起來上面肯尼的回答指向了引用問題中的正確解決方案,但可能帶走了錯誤的概念。 UIView類參考明確提出了一種系統,可以使sizeToFit與您的自定義視圖相關。

覆蓋sizeThatFits ,而不是sizeToFit

您的自定義UIView需要覆蓋sizeThatFits以返回“適合接收者子視圖的新大小”,但是您希望計算它。 您甚至可以使用另一個答案中的數學來確定您的新尺寸(但無需重新創建內置sizeToFit系統)。

sizeThatFits返回與其狀態相關的數字后,在自定義視圖上調用sizeToFit將開始導致預期的調整大小。

sizeThatFits工作原理

如果沒有覆蓋, sizeThatFits只返回傳入的大小參數(默認為self.bounds.size來自sizeToFit調用。雖然我只有幾個關於這個問題的來源,但似乎看不到傳入的大小作為嚴格的要求。

[ sizeThatFits ] 返回適合傳遞給它的約束的控件的“最合適”大小。 如果無法滿足約束,該方法可以(強調他們的)決定忽略這些約束。

查看無法讓 UIView sizeToFit 做任何有意義的事情

要點是sizeToFit是供子類覆蓋的,並且在 UIView 中不做任何事情。 它為UILabel做一些事情,因為它覆蓋了sizeThatFits:sizeToFit

更新了@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
    }
}

老問題,但你也可以用遞歸函數來做到這一點。

您可能想要一個無論有多少子視圖和子子視圖都始終有效的解決方案,...

更新:前一段代碼只有一個 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)
        }
    }
}

這是已接受答案的快速版本,也是很小的變化,而不是擴展它,此方法將視圖作為變量獲取並返回。

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

任何動態添加所有這些子視圖的模塊都必須知道將它們放在哪里(以便它們正確關聯,或者它們不會重疊等)一旦你知道了,加上當前視圖的大小,加上當前視圖的大小子視圖,您擁有了確定是否需要修改封閉視圖所需的一切。

當您創建自己的 UIView 類時,請考慮覆蓋子類中的 IntrinsicContentSize。 操作系統調用此屬性以了解建議的視圖大小。 我將把 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*/);
        }
    }
}

返回的大小完全取決於您的設計。 例如,如果您剛剛添加了一個標簽,那么寬度和高度就是該標簽的寬度和高度。 如果您有更復雜的視圖,則需要返回適合所有子視圖的大小。

盡管已經有很多答案,但沒有一個適用於我的用例。 如果您使用 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]
        }
    }
}
[myView sizeToFit];

應該工作,你為什么不檢查 CGRect 前后?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM