在添加子视图后,应该有一种方法可以调整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实现它的方式,正如评论中提到的那样。首先,让我们看一下它的工作原理:
正如下图所示,我们假设一些子视图位于外面,因此我们需要做两件事情来使其看起来更好,而不影响子视图的定位:
图片胜过千言万语。
代码胜过亿万言语。这是解决方案:
@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) }
看起来Kenny的答案上面指向了参考问题的正确解决方案,但可能已经误解了错误的概念。UIView
类引用明确建议了一个系统,使sizeToFit
适用于您的自定义视图。
sizeThatFits
,而不是sizeToFit
UIView
需要覆盖sizeThatFits
以返回“适合接收者子视图的新大小”,但是您可以根据自己的需要计算。您甚至可以使用另一个答案中的数学来确定您的新大小(但不重新创建内置的sizeToFit
系统)。
在sizeThatFits
返回与其状态相关的数字之后,对自定义视图调用sizeToFit
将开始导致预期的调整大小。
sizeThatFits
如何工作sizeThatFits
只是返回传入的尺寸参数(对于从sizeToFit
调用而言,默认为self.bounds.size
)。虽然我只在couple sources上看到这个问题,但似乎传入的尺寸并不被视为严格要求。
[
sizeThatFits
] 返回最适合符合传递给它的约束条件的控件的大小。如果无法满足约束条件,该方法可以(强调他们的话)决定忽略约束条件。
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)
}
}
}
UIView
和 CALayer
之间共享此代码时才有用。如果是针对 UIView
子类型,则只需使用普通扩展而无需使用协议。 - Mazyod虽然已经有很多答案了,但没有一个适用于我的情况。如果您使用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]
}
}
}
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*/);
}
}
}
这个返回的大小完全取决于你的设计。例如,如果你只添加了一个标签,那么宽度和高度就是该标签的宽度和高度。如果你有更复杂的视图,你需要返回适合所有子视图的大小。
无论是哪个模块动态添加了所有这些子视图,都必须知道将它们放在哪里(以便它们正确关联或不重叠等)。一旦你知道了这一点,再加上当前视图的大小和子视图的大小,你就有了确定是否需要修改封闭视图的所有必要信息。
tmpFrame = CGRectUnion(tmpFrame, v.frame)
,其中tmpFrame将在for循环之前声明为CGRectZero。 - mrd3650