UIView ScreenSize bei Verwendung von Size Classes.

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • UIView ScreenSize bei Verwendung von Size Classes.

    Hallo zusammen,

    ich probiere mich derzeit an Size Classes. Ich erstelle ein UIView über IB auf dem MainView "ANY - ANY". Die Grösse beträgt w350 x h450.
    Analog dazu setze ich nun im "Iphone Portrait" - "Compact -ANY" eine neue Breite von w300 x h400. Soweit so gut. Der Screen wird wie gewünscht dargestellt.

    Nun zum Problem. Auf dem obergenannten View werden 10 symmetrische Buttons dargestellt. Zur Berechnung dient die aktuelle Screen - Size. Diese bleibt jedoch immer im Standart - Format 350x450.
    Auf dem iphone Simulator hätte ich jetzt erwartet das ich mit

    Quellcode

    1. ​NSLog(@"Size of Keyboard is:%@", NSStringFromCGSize(self.keyboardLayer.frame.size));
    die größe 300x400 zurück bekomme.

    Was müsste ich tun um an die richtige Screen Size zu kommen?

    Anbei noch ein Screenshot mit dem Constant wert und dem Compact -ANY Value. Zudem unten die NSLog Ausgabe im unteren Teil.

    1000 Dank im vorraus.

    Grüsse Dave
    Dateien
  • DaveOSX schrieb:

    Dort müsste doch bereits gezeichnet sein und er müsste mir doch die richtige Size zurückgeben, oder wo ist mein Denkfehler?

    viewDidLoad: wörtlich für View wurde geladen. Woher? Aus dem Storyboard oder dem NIB, und das kann passieren, auch ohne dass der View angezeigt wird. Oder andersherum: Das passiert, wenn irgendwer den view-Getter des Viewcontrollers aufruft.

    Jetzt könnte man auf die Idee kommen, dass es Methoden für die Anzeige gibt. Zapperlot, und die gibt es: View ist erschienen oder viewDidAppear:
    ;) :D
    „Meine Komplikation hatte eine Komplikation.“
  • Hallo macmoonshine , Michael,

    danke für die schnelle Antwort. Gut hätten wir das geklärt. Danke.

    Der Richtige Weg wäre also:
    1. in viewDidLoad --> setzen der parameter der Subview.

    Quellcode

    1. self.keyboardLayer.scaleFactor = [NSNumber numberWithFloat:0.85];
    2. self.keyboardLayer.padding = [NSNumber numberWithFloat:30.0];
    3. self.keyboardLayer.heightOffset = [NSNumber numberWithFloat:45.0];


    2. in viewDidAppear --> zeichnen der gewünschten buttons

    Quellcode

    1. [self.keyboardLayer drawButtons];


    Funktioniert soweit.

    Frage: Die Methode "drawButton" in der Klasse KeyboardLayer wurde von mir so geschrieben, dass diese zuvor Überprüft ob bereits die Button auf dem Subview erstellt wurden.
    Wenn ja, dann alle löschen und neu zeichnen. Grund: Ich möchte erreichen, dass wenn das Device von Portrait in Landscape wechselt die Buttons innerhalb des neu skalierten Views
    neu gezeichnet werden. Also sprich, die SubViewSize ändert sich erneut.
    Worüber ich mir jetzt aber nicht sicher bin ist, welche Methode sollte zur Erkennung der Drehung verwendet werden. Es gibt zum einem "viewDidLayoutSubviews" sowie seit iOS8 die neue "traitCollectionDidChange". Beide werden noch vor dem viewDidAppear aufgerufen. Was bedeutet, ich würde derzeit beim starten der App zweimal zeichnen. Einmal in trait.. und danach beim Aufruf des viewDidAppear.
    Beim derzeitigen testen habe ich den Aufruf zum zeichnen nur im "traitCollectionDidChange". Dort werden beide fälle beim start sowie beim drehen abgedeckt. Bloss ob das er Richtige weg ist weiß ich nicht?

    Grüsse Dave.
  • DaveOSX schrieb:

    2. in viewDidAppear --> zeichnen der gewünschten buttons

    Das ist nur insofern in Ordnung, sofern diese Methode nicht zeichnet. Zeichenmethoden von Views und Layern rufst Du niemals direkt auf. Cocoa Touch bittet immer darum. ;)

    Auch für die Drehung kannst Du viewDidLayoutSubviews verwenden, da der View das Layout nach einer Drehung in der Regel neu berechnen muss.
    „Meine Komplikation hatte eine Komplikation.“
  • diese Methode enthält das löschen der Buttons auf dem View. Danach rufe ich innerhalb dieser Methode die Methode zum neu zeichen auf. Also berechnen und adden auf dem Subview.
    Anmerkung. KeyboardLayer ist ein UIView der 10 kleine SubViews beinhaltet. Siehe Anhang.

    ich vermute auf Basis Deiner Ausführung, dass ich das falsch mache. Ich müsste quasi im ViewController - viewDidLayoutSubView die Methode setNeedDisplay - (drawRect) der Klasse KeyboardLayer aufrufen, richtig? dort müsste ich dann alles neu berechnen und auch zeichnen, oder?
    Dateien
  • Nein, Layout und Zeichnen sind zwei getrennte Vorgänge, und die Ergebnisse dieser beiden Operationen kann sich Cocoa Touch getrennt voneinander merken. Du kannst neu zeichnen, ohne das Layout neu zu berechnen und das Layout neuberechnen ohne neu zeichnen zu müssen.

    Wenn Dein View sein Layout selbständig anpassen können soll, sollte er dafür layoutSubviews überschreiben, sonst nichts. Wenn Du in dem View etwas zeichnen willst, überschreibst Du drawRect:, und machst ansonsten nichts. Über setNeedsDisplay und setNeedsLayout kannst Du das Zeichnen oder Layouten explizit antriggern. Letzteres sollte aber bei einer Viewrotation automatisch erfolgen.
    „Meine Komplikation hatte eine Komplikation.“
  • Ok, sorry bezogen auf mein Beispiel kann ich Dir nicht mehr folgen.
    Ich zeige Dir jetzt meine Programm - Struktur und Du hilfst mir bitte auf die Sprünge was ich alles falsch gemacht habe.
    Noch als Anmerkung, das ist nur vereinfachter Code und nicht der gesamte!

    Im ViewController:

    Quellcode

    1. // Setzen der Parameter der im IB erzeugten UIview vom Typ Keyboardlayer
    2. - (void)viewDidLoad {
    3. [super viewDidLoad];
    4. self.keyboardLayer.scaleFactor = [NSNumber numberWithFloat:0.85];
    5. self.keyboardLayer.padding = [NSNumber numberWithFloat:30.0];
    6. self.keyboardLayer.heightOffset = [NSNumber numberWithFloat:45.0];
    7. }
    8. -(void)viewDidLayoutSubviews{
    9. // Wenn View dreht ruft er diese Methode auf. Diese Löscht die Buttons und weisst danach an neu zu Zeichnen.
    10. [self.keyboardLayer updateButton];
    11. }
    Alles anzeigen


    IM Keyboradlayer:

    Quellcode

    1. -(void)drawButtons{
    2. // Die Berechnung habe ich rausgenommen da diese nicht wichtig ist. Diese wird dann an das Objekt unten mit calculatedFramePosition übergeben.
    3. self.rButton = [[RoundButton alloc]initWithFrame:calculatedFramePosition];
    4. self.rButton.title.text = [NSString stringWithFormat:@"%i", (int)number];
    5. self.rButton.subTitle.text = [letters valueForKey:[NSString stringWithFormat:@"%i", (int)number]];
    6. self.rButton.delegate = self;
    7. [self addSubview:self.rButton];
    8. self.rButton = nil;
    9. }
    10. -(void)updateButton{
    11. for (RoundButton *button in self.subviews) {
    12. if ([button isKindOfClass:[RoundButton class]]) {
    13. [button removeFromSuperview];
    14. }
    15. }
    16. // Anweisung neu zu Zeichnen.
    17. [self drawButtons];
    18. }
    Alles anzeigen


    Diese Version funktioniert ist aber wahscheinlich von der Ausführung her nicht richtig. Was wäre jetzt am Beispiel gesehen die Richtige Vorgehensweise?

    Grüsse Dave
  • Ich würde so vorgehen:
    Der View erzeugt die Subbuttons, wenn Du ihn erzeugst. Du überschreibst layoutSubviews in Deiner Viewklasse. Darin weist Du jedem Button seine Position in dem Bounds-rechteck des Views zu. Fertig, den Rest macht Cocoa-Touch.

    Zu Deinem Code habe ich aber noch ein paar Fragen:
    Warum verwendest Du in drawButtons eine Property rButton anstatt einer lokalen Variablen?
    Warum benennst Du Methoden nicht danach, was sie machen? (drawButtons zeichnet nicht und updateButtons aktualisiert nicht)
    Warum baust Du jedes mal den View neu zusammen, wenn sich das Layout ändert?
    Wozu brauchen Deine Buttons ein Delegate?
    „Meine Komplikation hatte eine Komplikation.“

  • Zu Deinem Code habe ich aber noch ein paar Fragen:
    Warum verwendest Du in drawButtons eine Property rButton anstatt einer lokalen Variablen?
    Warum benennst Du Methoden nicht danach, was sie machen? (drawButtons zeichnet nicht und updateButtons aktualisiert nicht)
    Warum baust Du jedes mal den View neu zusammen, wenn sich das Layout ändert?
    Wozu brauchen Deine Buttons ein Delegate?


    Warum verwendest Du in drawButtons eine Property rButton anstatt einer lokalen Variablen?
    Das ist Blödsinn, da gebe ich Dir recht. Das sollte natürlich eine lokale Variable sein. Ich hatte das mal angedacht um einen einzelnen Button von aussen zu manipulieren.
    Geht ja aber nicht bei dieser Version, wird überarbeitet.

    Warum benennst Du Methoden nicht danach, was sie machen? (drawButtons zeichnet nicht und updateButtons aktualisiert nicht)
    ja könnte besser gewählt sein. Ich habe den Code mehrmals überarbeitet und die Bezeichnung dabei aussen vor gelassen. Schlamperei ich weiß... :)

    Warum baust Du jedes mal den View neu zusammen, wenn sich das Layout ändert?
    Das war mein erster Gedanke. Wenn die Buttons einmal gezeichnet sind, einfach ein neues frame/bounds zuweisen. Das werde ich versuchen als nächstes zu implementieren.
    Ich weiß nur nicht ob sich der Aufwand gegenüber des Löschen und neu setzen lohnt.

    Wozu brauchen Deine Buttons ein Delegate?
    Weil die Button in Wahrheit keine Buttons sind, sondern UIView's die ein GestüreRegonizer besitzen. Ich habe diese Version mal für ein anders Projekt verwendet und wollte es hier wiederverwenden. Wenn ich im ViewController jetzt ein Textfeld habe welche die Zahlen aufnehmen soll, muss ich ja wissen welcher Button (View) gedrückt wurde um danach sein Label zu übergeben. Also arbeite ich hier mit Protokollen und Delegates. Ein UIButton event müsste ich ja auch an einen Controller übergeben, somit denke ich, wäre es das gleiche.
    Zudem war noch animation der Buttons ein Thema. Mit Views geht das sicherlich besser....

    Grüsse Dave

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von DaveOSX ()

  • DaveOSX schrieb:

    Ich weiß nur nicht ob sich der Aufwand gegenüber des Löschen und neu setzen lohnt.

    Du kannst ja die Berechnung weiterverwenden. Anstatt einen View zu erzeugen, musst Du den i-ten Subview holen (sehr kniffelig) und dessen frame-Property den berechneten Frame zuweisen (nahezu unlösbar). :D ;) Dabei musst Du die Buttons so in Deinen View einfügen, dass der View mit der 0 an Position 0, der mit der 1 an Position 1, usw. steht.

    Anstelle der Frames kannst Du natürlich auch nur den Mittelpunkt der Views ändern.

    DaveOSX schrieb:

    Weil die Button in Wahrheit keine Buttons sind, sondern UIView's die ein GestüreRegonizer besitzen. Ich habe diese Version mal für ein anders Projekt verwendet und wollte es hier wiederverwenden. Wenn ich im ViewController jetzt ein Textfeld habe welche die Zahlen aufnehmen soll, muss ich ja wissen welcher Button (View) gedrückt wurde um danach sein Label zu übergeben. Also arbeite ich hier mit Protokollen und Delegates.
    Ein UIButton event müsste ich ja auch an einen Controller übergeben, somit denke ich, wäre es das gleiche.

    Wenn Du als Basisklasse UIControl verwendest, kannst Du Target-Action verwenden.
    „Meine Komplikation hatte eine Komplikation.“
  • Hallo macmoonshine,

    ich benötige nochmal deine Hilfe zur Ausführung.
    Ich habe nun die Klasse so umgeschrieben das erst das Keyboard mit den SubButtons initialisiert wird und danach in der Methode layoutSubviews die benötigten Frames gesetzt werden.

    Fragen:
    1. Sollten man schon beim initWithFrame die berechneten Frames setzen oder reicht dort vorerst ein CGRectZero?
    Im layoutSubviews werden dann die berechneten SizeFrames gesetzt.
    Warum die Frage: Natürlich wäre es besser schon beim initWithFrame die benötigten CGRect werte zu setzten.
    Dann würde das alles aber zweimal ausgeführt. Einmal beim Initialisieren und einmal wenn layoutSubview aufgerufen wird. Was ja kurz nach Init passiert.


    2. Ein weiterer Punkt den ich beobachtet habe ist ein unterschiedliches verhalten. Wenn ich das neu setzen des SizeFrame in layoutSubview unterbringe dann werden die Buttons während der Drehung aus der Mitte des Buttons heraus neu gezeichnet. Das sieht im SlowMotion des Iphone Simulator nicht sehr schön aus. Auf dem Device selber ist das auch zu erkennen, geht aber viel schneller und ist nur bei genauem hinsehen zu bemerken.
    Dann habe ich mal das ganze in drawRect untergebracht. Dort ist es so, dass erst neu gezeichnet wird und danach wird erst gedreht. Siehe Bilder im Anhang.
    Was ist nun richtig? Von der Flüssigkeit her ist drawRect die besser Wahl. Von der Richtigkeit her aber layoutSubview. Wie kann ich diese "verzerren" der Buttons unter layoutSubviews verhindern?

    Quellcode

    1. -(void)drawRect:(CGRect)rect{
    2. [super drawRect:rect];
    3. }
    4. -(void)layoutSubviews{
    5. [super layoutSubviews];
    6. [self calculatePositionOfSymetricButtons];
    7. for (int i = 0; i < 11; i++) {
    8. if (i == 9 || i == 11 ) {
    9. continue;
    10. }
    11. RoundButton *buttons = (RoundButton*)[_keyBoardButtons objectAtIndex:i];
    12. CGRect newFrame = [[_calculateFrames objectAtIndex:i]CGRectValue];
    13. buttons.frame = newFrame;
    14. }
    15. }
    Alles anzeigen
    Dateien
    • layoutSubviews.png

      (366,69 kB, 254 mal heruntergeladen, zuletzt: )
    • drawRect.png

      (443,02 kB, 234 mal heruntergeladen, zuletzt: )
    • Portrait.png

      (308,27 kB, 227 mal heruntergeladen, zuletzt: )
    • Landscape.png

      (344 kB, 227 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von DaveOSX ()

  • DaveOSX schrieb:

    Warum die Frage: Natürlich wäre es besser schon beim initWithFrame die benötigten CGRect werte zu setzten.

    Nein, erstens ist die korrekte Größe des Views bei initWithFrame: nicht bekannt, zweitens verwendet Cocoa-Touch diese Methode nicht, wenn der View in einem Storyboard liegt und drittens ist der Ansatz über layoutSubviews wesentlich fehlertoleranter und flexibler.

    DaveOSX schrieb:

    Dann würde das alles aber zweimal ausgeführt. Einmal beim Initialisieren und einmal wenn layoutSubview aufgerufen wird. Was ja kurz nach Init passiert.

    Dann lass' das Layouten in initWithFrame: doch einfach weg.

    DaveOSX schrieb:

    Was ist nun richtig? Von der Flüssigkeit her ist drawRect die besser Wahl. Von der Richtigkeit her aber layoutSubview.

    Richtig ist: drawRect: zeichnet und layoutSubviews layoutet. Dazu kann ich nichts sagen, da ich Deine drawRect:-Methode der Buttons nicht kenne. Die drawRect:-Methode im Listing kannst Du Dir jedenfalls sparen.

    DaveOSX schrieb:

    Wie kann ich diese "verzerren" der Buttons unter layoutSubviews verhindern?

    Vermutlich zeichnest Du nicht (nur) in drawRect:. Wie bereits gesagt, in der Regel zeichner Cocoa-Touch den Inhalt eines erst dann neu, wenn man direkt oder indirekt setNeedsDisplay aufruft. Wie zeichnest Du denn Deine Buttons?
    „Meine Komplikation hatte eine Komplikation.“
  • Hallo, danke für die Ausführungen,

    macmoonshine schrieb:

    Richtig ist: drawRect: zeichnet und layoutSubviews layoutet. Dazu kann ich nichts sagen, da ich Deine drawRect:-Methode der Buttons nicht kenne. Die drawRect:-Methode im Listing kannst Du Dir jedenfalls sparen.


    Wenn ich den Inhalt aus layoutSubview heraus kopiere und in drawRect einsetze kommt es zu dem zuvor beschriebenen Verhalten. Deswegen ist die drawRect im Listing leer.
    Es einfach nur eine schleife die jedem Button einen neuen frame Pos zuweist.


    Anbei nochmal bisschen code zum Ablauf des Programm:

    1. KeyboardLayer - Class

    Quellcode

    1. -(id)initWithCoder:(NSCoder *)aDecoder{
    2. self = [super initWithCoder:aDecoder];
    3. if (self) {
    4. [self keyboardInitialisation];
    5. }
    6. return self;
    7. }
    8. - (id)initWithFrame:(CGRect)frame{
    9. self = [super initWithFrame:frame];
    10. if (self) {
    11. [self keyboardInitialisation];
    12. }
    13. return self;
    14. }
    Alles anzeigen



    Buttons Initialisieren:

    Quellcode

    1. -(void)keyboardInitialisation{
    2. // Hier werden die Buttons in einer Schleife erstellt und auf dem View addSubView gesetzt.
    3. //Button initialization with CGRectZero
    4. _rButton = [[RoundButton alloc]initWithFrame:CGRectZero];
    5. _rButton.title.text = [NSString stringWithFormat:@"%i", (int)number];
    6. _rButton.subTitle.text = [letters valueForKey:[NSString stringWithFormat:@"%i", (int)number]];
    7. _rButton.delegate = self;
    8. [_keyBoardButtons addObject:_rButton];
    9. [self addSubview:_rButton];
    10. _rButton = nil;
    11. }
    12. }
    Alles anzeigen



    Methode layoutSubview:

    Quellcode

    1. -(void)layoutSubviews{
    2. [super layoutSubviews];
    3. [self calculatePositionOfSymetricButtons];
    4. for (int i = 0; i < 11; i++) {
    5. if (i == 9 || i == 11 ) {
    6. continue;
    7. }
    8. RoundButton *buttons = (RoundButton*)[_keyBoardButtons objectAtIndex:i];
    9. CGRect newFrame = [[_calculateFrames objectAtIndex:i]CGRectValue];
    10. buttons.frame = newFrame;
    11. }
    12. }
    Alles anzeigen



    Im ViehController wird das Keyboard erstellt und die Parameter gesetzt.

    Quellcode

    1. - (void)viewDidLoad {
    2. [super viewDidLoad];
    3. self.keyboardLayer.scaleFactor = [NSNumber numberWithFloat:0.85];
    4. self.keyboardLayer.padding = [NSNumber numberWithFloat:10.0];
    5. self.keyboardLayer.heightOffset = [NSNumber numberWithFloat:35.0];
    6. self.keyboardLayer.screenCenterPos = .0f;
    7. self.keyboardLayer.delegate = self;
    8. }
    9. -(void)viewDidLayoutSubviews{
    10. [self.keyboardLayer setNeedsLayout];
    11. }
    Alles anzeigen


    Bei jeder Drehung wird viewDidLayoutSubviews im ViewController aufgerufen und ich setze setNeedLayout. Mehr mache ich eigentlich nicht.

    Mir geht es speziell darum, zu lernen, ordentlichen Code zu schreiben. Etwas zu schreiben was funktioniert ist das eine. Qualität rein bringen ist das andere. Und an dem Punkt bin ich jetzt.
  • DaveOSX schrieb:

    Bei jeder Drehung wird viewDidLayoutSubviews im ViewController aufgerufen und ich setze setNeedLayout. Mehr mache ich eigentlich nicht.

    Warum machst Du das? Did Layout besagt doch, dass der Layout-Prozess gerade abgeschlossen wurde, und Du schubst den dann wieder an. Schmeiß das mal raus.

    Falls das Layout dann nicht stimmt, überschreibst Du viewWillLayoutSubviews.
    „Meine Komplikation hatte eine Komplikation.“