Ne devrait-il pas y avoir un moyen de redimensionner le cadre d'un UIView après avoir ajouté des sous-vues afin que le cadre ait la taille requise pour inclure toutes les sous-vues? Si vos sous-vues sont ajoutées dynamiquement, comment pouvez-vous déterminer la taille que doit avoir le cadre de la vue Conteneur? Cela ne marche pas:
[myView sizeToFit];
Check out Avoir du mal à faire en sorte que UIView sizeToFit fasse quelque chose de significatif
L'essentiel est que sizeToFit
concerne les sous-classes, et ne fait rien dans UIView. Il fait des choses pour UILabel
, car il remplace sizeThatFits:
qui est appelé par sizeToFit
Vous pouvez également ajouter le code suivant pour calculer la position des sous-vues.
[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
J'avais besoin d'adapter les sous-vues avaient un point d'origine négatif, et CGRectUnion
est la manière de procéder ObjC, honnêtement, comme quelqu'un l'a mentionné dans les commentaires. Tout d’abord, voyons comment cela fonctionne:
Comme vous pouvez le voir ci-dessous, nous supposons que certaines sous-vues sont à l'extérieur. Nous devons donc faire deux choses pour que cette image soit belle, sans affecter le positionnement de ces dernières:
Une image vaut mieux que mille mots.
Le code vaut un milliard de mots. Voici la 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
J'ai pensé que ce serait amusant d'écrire dans Swift 2.0 .. J'avais raison!
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
}
}
Et la preuve de terrain de jeu:
Remarquez que la visualAidView
ne bouge pas et vous aide à voir comment la superview
redimensionne tout en maintenant les positions des sous-vues.
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) }
Il semble que la réponse de Kenny ci-dessus indique la bonne solution dans la question référencée , mais a peut-être enlevé le mauvais concept. La référence de classe UIView
suggère définitivement un système permettant de rendre sizeToFit
pertinent pour vos vues personnalisées.
sizeThatFits
, pas sizeToFit
Votre UIView
s personnalisé doit remplacer sizeThatFits
pour renvoyer "une nouvelle taille qui corresponde aux sous-vues du destinataire", quel que soit le calcul que vous souhaitez faire. Vous pouvez même utiliser le calcul tiré de une autre réponse pour déterminer votre nouvelle taille (mais sans recréer le système sizeToFit
intégré).
Une fois que sizeThatFits
a renvoyé les numéros correspondant à son état, les appels à sizeToFit
sur vos vues personnalisées commenceront, entraînant ainsi les redimensionnements attendus.
sizeThatFits
Sans substitution, sizeThatFits
renvoie simplement le paramètre de taille transmis (défini par défaut sur self.bounds.size
pour les appels de sizeToFit
. Bien que je n’ai qu’un couple sources sur le problème, il semble que la taille transmise est ne pas être vu comme une demande stricte.
[
sizeThatFits
] renvoie la taille "la plus appropriée" pour le contrôle qui correspond aux contraintes qui lui ont été transmises. La méthode can (leur accentuation) décide d’ignorer les contraintes si elles ne peuvent pas être satisfaites.
Mise à jour de la réponse de Mazyod à Swift 3.0, a fonctionné à merveille!
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
}
}
Ancienne question mais vous pouvez aussi le faire avec une fonction récursive.
Vous voudrez peut-être une solution qui fonctionne toujours, peu importe le nombre de sous-vues et de sous-vues, ...
Mise à jour: le code précédent ne comportait qu'une fonction de lecture, maintenant également un outil de définition.
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)
}
}
}
Lorsque vous créez votre propre classe UIView, pensez à remplacer IntrinsicContentSize dans la sous-classe. Le système d'exploitation appelle cette propriété pour connaître la taille recommandée de votre vue. Je vais mettre l'extrait de code pour C # (Xamarin), mais l'idée est la même:
[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*/);
}
}
}
Cette taille retournée dépend entièrement de votre conception. Par exemple, si vous venez d'ajouter une étiquette, largeur et hauteur ne sont que la largeur et la hauteur de cette étiquette. Si vous avez une vue plus complexe, vous devez renvoyer la taille qui convient à toutes vos sous-vues.
Voici une version Swift de la réponse acceptée, également une petite modification, au lieu de l'étendre, cette méthode obtient la vue sous forme de variable et la renvoie.
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
}
Heres la version extensible:
/// 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)
}
Quel que soit le module qui ajoute de manière dynamique toutes ces sous-vues, il doit savoir où les placer (afin qu'elles soient correctement mises en relation, ou qu'elles ne se chevauchent pas, etc.) sous-vue, vous avez tout ce dont vous avez besoin pour déterminer si la vue englobante doit être modifiée.