web-dev-qa-db-fra.com

iPhone UIView - Redimensionner le cadre pour ajuster les sous-vues

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];
40
sol

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

16
Kenny Winker

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
43
user1204936

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:

  1. Déplace le cadre de la vue en haut à gauche
  2. Déplacez les vues secondaires dans la direction opposée pour annuler l'effet.

Une image vaut mieux que mille mots.

demonstration

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

playground image

23
Mazyod

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.

Remplacer sizeThatFits, pas sizeToFit

Votre UIViews 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.

Comment fonctionne 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.

18
patridge

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
    }
}
2
mr. sudo

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)
        }
    }
}
1
R Menke

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.

0
Ibrahim

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

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.

0
hotpaw2