Sollte es nicht möglich sein, den Frame einer UIView nach dem Hinzufügen von Subviews so zu skalieren, dass der Frame die Größe hat, die zum Einschließen aller Subviews erforderlich ist? Wenn Ihre Unteransichten dynamisch hinzugefügt werden, wie können Sie dann bestimmen, wie groß der Rahmen der Containeransicht sein muss? Das funktioniert nicht:
[myView sizeToFit];
Check out Probleme mit UIView sizeToFit, etwas Sinnvolles zu tun
Das Wesentliche ist, dass sizeToFit
für Unterklassen gilt, die außer Kraft gesetzt werden, und in UIView nichts tut. Es macht Sachen für UILabel
, da es sizeThatFits:
überschreibt, der von sizeToFit
aufgerufen wird.
Sie können auch den folgenden Code hinzufügen, um die Position der Unteransichten zu berechnen.
[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
Ich musste die Unteransichten anpassen, die einen negativen Ursprungspunkt hatten, und CGRectUnion
ist die Art und Weise von ObjC, ehrlich gesagt, wie jemand in den Kommentaren erwähnt. Zuerst wollen wir sehen, wie es funktioniert:
Wie Sie unten sehen können, gehen wir davon aus, dass einige Unteransichten außerhalb liegen. Daher müssen wir zwei Dinge tun, damit dies gut aussieht, ohne die Positionierung der Unteransichten zu beeinträchtigen:
Ein Bild sagt mehr als tausend Worte.
Code ist eine Milliarde Wörter wert. Hier ist die Lösung:
@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
Ich dachte, es würde Spaß machen, in Swift 2.0 zu schreiben. Ich hatte recht!
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
}
}
Und der Spielplatz Beweis:
Beachten Sie, dass sich die Variable visualAidView
nicht bewegt, und zeigt Ihnen, wie die Größe der superview
geändert wird, während die Positionen der Unteransichten beibehalten werden.
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) }
Es sieht so aus, als ob die Antwort von Kenny auf die richtige Lösung in der verwiesenen Frage verweist, aber möglicherweise das falsche Konzept entfernt hat. Die UIView
-Klassenreferenz schlägt definitiv ein System vor, um sizeToFit
für Ihre benutzerdefinierten Ansichten relevant zu machen.
sizeThatFits
, nicht sizeToFit
Ihre benutzerdefinierten UIView
s müssen sizeThatFits
überschreiben, um "eine neue Größe zurückzugeben, die zu den Unteransichten des Empfängers passt". Dies möchten Sie jedoch berechnen. Sie können sogar die Mathematik aus einer anderen Antwort verwenden, um Ihre neue Größe zu bestimmen (jedoch ohne das eingebaute sizeToFit
-System neu zu erstellen).
Nachdem sizeThatFits
für seinen Status relevante Zahlen zurückgegeben hat, werden Aufrufe von sizeToFit
in Ihren benutzerdefinierten Ansichten mit den erwarteten Größenänderungen ausgelöst.
sizeThatFits
funktioniertOhne Überschreibung gibt sizeThatFits
einfach den übergebenen Größenparameter zurück (standardmäßig self.bounds.size
für Aufrufe von sizeToFit
... Während ich nur ein couplesources habe, scheint die übergebene Größe zu sein nicht als strikte Forderung zu sehen.
[
sizeThatFits
] gibt die "am besten geeignete" Größe für das Steuerelement zurück, das den übergebenen Einschränkungen entspricht. Die Methode can (Hervorhebung ihrer) kann die Einschränkungen ignorieren, wenn sie nicht erfüllt werden können.
@ Mazyods Antwort auf Swift 3.0 aktualisiert, funktionierte wie ein Zauber!
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
}
}
Alte Frage, aber Sie könnten dies auch mit einer rekursiven Funktion tun.
Möglicherweise möchten Sie eine Lösung, die immer funktioniert, unabhängig von der Anzahl der Unteransichten und Unteransichten.
Update: Vorheriges Stück Code hatte nur eine Getter-Funktion, jetzt auch einen 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)
}
}
}
Wenn Sie Ihre eigene UIView-Klasse erstellen, sollten Sie IntrinsicContentSize in der Unterklasse überschreiben. Diese Eigenschaft wird vom Betriebssystem aufgerufen, um die empfohlene Größe Ihrer Ansicht zu ermitteln. Ich werde Code-Snippet für C # (Xamarin) einfügen, aber die Idee ist die gleiche:
[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*/);
}
}
}
Diese zurückgegebene Größe hängt vollständig von Ihrem Design ab. Wenn Sie beispielsweise gerade ein Etikett hinzugefügt haben, entspricht Breite und Höhe nur der Breite und Höhe dieses Etiketts. Wenn Sie eine komplexere Ansicht haben, müssen Sie die Größe zurückgeben, die für alle Ihre Unteransichten passt.
Hier ist eine schnelle Version der akzeptierten Antwort, auch eine kleine Änderung. Statt sie zu erweitern, ruft diese Methode die Ansicht als Variable ab und gibt sie zurück.
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
}
Hier ist die Erweiterungsversion:
/// 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)
}
Unabhängig von dem Modul, das alle diese Unteransichten dynamisch hinzufügte, musste es wissen, wo sie platziert werden sollten (damit sie sich richtig beziehen, oder dass sie sich nicht überlappen usw.) Wenn Sie das wissen, plus die Größe der aktuellen Ansicht plus die Größe der Unteransicht haben Sie alles, um festzustellen, ob die umschließende Ansicht geändert werden muss.