Constraints Problem mit unterschiedlichen Display Größen

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

  • Constraints Problem mit unterschiedlichen Display Größen

    Hallo,

    wie dem Titel schon zu entnehmen ist habe ich aktuell das Problem das eine meiner Views nicht auf allen Gerätegrößen gleich aussieht. Das hat schätze ich mehrere Gründe als lediglich die Constraints aber ich komme einfach erstmal zum groben Aufbau meiner View, damit ihr euch ungefähr vorstellen könnt wie das ganze ausschaut.
    Da die View in einem Navigation Controller eingebunden ist habe ich ganz oben eine Nav Bar.
    Direkt unter dieser befindet sich eine ImageView mit den maßen 49 x 49 in dem sich ein Profilbild befinden soll. Ungefähr 8 Pixel darunter befindet sich eine ScrollView mit den maßen 375 x 375.

    Da ich das ganze standardmäßig auf die Größe des iPhone 8 anpasse habe ich jetzt ein Problem wenn ich den Simulator auf iPhone X oder iPhone 8 Plus stelle. Im wesentlichen habe ich auch nur ein Problem mit der ScrollView da sich diese je nach Gerät anders verhält. Den Rest kriege ich gehändelt.

    Bei der ScrollView gibt es zudem eine Besonderheit da ich in dieser die Bilder auslese die ein User hochgeladen hat. Aus dem Grund habe ich auch zum Teil Code dazu geschrieben in dem die maße der ScrollView eine entscheidende Rolle spielen. Diese Code schaut so aus:

    Quellcode

    1. for picture in questionToPicture {
    2. for picture1 in picture where picture1.key == questionLabelText {
    3. VotePageControl.numberOfPages = picture1.value.count
    4. for (index,picture2) in picture1.value.enumerated() {
    5. let imageToDisplay: URL? = URL(string: allQuestionPictureURL + picture2)
    6. let questionImageView: UIImageView = UIImageView(image:nil)
    7. if let imageUrl = imageToDisplay {
    8. questionImageView.sd_setImage(with: imageUrl)
    9. }
    10. questionImageView.contentMode = .scaleAspectFit
    11. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    12. contentWidth += view.frame.width
    13. VotingScrollView.addSubview(questionImageView)
    14. questionImageView.frame = CGRect(x: xCoordinate, y: (view.frame.height / 2) - 335, width: 375, height: 375)
    15. }
    16. }
    17. }
    18. VotingScrollView.contentSize = CGSize(width: contentWidth, height: 375)
    Alles anzeigen
    Wie ihr seht gebe ich die maße der ScrollView auch in dem Code so ein. Ich habe also bspw. beim iPhone 8 Plus das Problem das die Bilder in der ScrollView zu kurz raten was die Breite angeht da die 375px eben nur auf die maße eine normalen iPhone 8 passe. Beim iPhone X passt alles von der Breite her, nur bei der Höhe scheint es so als wenn das Bild in der ScrollView von irgendwas nach unten gezogen wird weswegen ich einen Teil der unteren Hälfte des Bildes nicht sehe und dabei entsteht auch zwischen Bild und Profilbild ein dickerer weißer Balken der aber wohl zur ScrollView gehört da dieser auf Berührung reagiert (ist wohl einfach nicht mit dem Bild gefüllt an der Stelle).

    Jetzt ist die Frage was ich da machen kann. Ich habe bereits alle Constraints auf dieser View entfernt um nochmal schön von null anzufangen. Wie gesagt habe ich am meisten Problem mit der ScrollView. Was kann ich also machen um das Problem mit der Breite und Höhe auf allen Geräten in den Griff zu bekommen und wie fern muss ich den Code dazu anpassen ?

    Ich habe im Netzt schon ein wenig geschaut ob es eventuell möglich wäre Größenangaben in Prozent anzugeben habe dabei nur etwa mit einem Multiplikator gefunden. Wollte jedoch erstmal hier nachfragen ob vielleicht jemand ein paar Tipps hat ?:D
  • Okay, ich gestehe, Deiner Beschreibung nicht ganz gefolgt zu sein. Trotzdem zwei Bemerkungen, die Dir vielleicht weiterhelfen:
    1. Verdrahte keine Maße fest in Deiner App, das ist die Anleitung zum Unglücklichsein: Spätestens beim nächsten neuen Display beißt Dich das in den Hintern.
    2. Überlege Dir, welche Abstände immer einen bestimmten (festen oder prozentualen) Wert haben sollen und definiere nur diese als Constraints. Die anderen - beispielsweise die View-Höhe - lässt Du einfach weg, dann ist diese automatisch passend.
    Bzgl. der Identifikation von Images solltest Du Dir m. E. etwas anderes ausdenken: Vielleicht Tags oder eine CollectionView mit entsprechender DataSource...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Befindet sich der o.a. Code in der viewDidLoad() Methode?

    Da ist der View zwar geladen, aber in der Größe, die Du im Interface Builder verwendet hast. Erst nach dem viewDidLoad() passt iOS die Größe bzw. das Layout des Views an das aktuelle Display an. Dies nervt schon sehr, ist aber leider so.

    Du musst den zum ScrollView hinzugefügten questionImageViews natürlich passende Constraints oder eine passende autoresizingMask verpassen, damit sich diese an die Größenänderung des ScrollViews anpassen.

    Alternativ kannst Du die Größenanpassung der questionImageViews auch per Code in viewDidLayoutSubviews() erledigen.
  • MCDan schrieb:

    Befindet sich der o.a. Code in der viewDidLoad() Methode?

    Da ist der View zwar geladen, aber in der Größe, die Du im Interface Builder verwendet hast. Erst nach dem viewDidLoad() passt iOS die Größe bzw. das Layout des Views an das aktuelle Display an. Dies nervt schon sehr, ist aber leider so.

    Du musst den zum ScrollView hinzugefügten questionImageViews natürlich passende Constraints oder eine passende autoresizingMask verpassen, damit sich diese an die Größenänderung des ScrollViews anpassen.

    Alternativ kannst Du die Größenanpassung der questionImageViews auch per Code in viewDidLayoutSubviews() erledigen.
    Ja der Code befindet sich in der viewDidLoad(). Muss auch sagen das ich durch deinen Kommentar schon ein Stück weiter gekommen bin und zwar habe ich es durch das Autoresizing tool geschafft das die VotingScrollView sich an die Größe des Displays/Geräts anpasst. Das einzige was jetzt nicht hinhaut ist der Inhalt also die questionImageView.

    Ich habe dazu aktuell folgenden Code geschrieben:

    Quellcode

    1. for picture in questionToPicture {
    2. for picture1 in picture where picture1.key == questionLabelText {
    3. VotePageControl.numberOfPages = picture1.value.count
    4. for (index,picture2) in picture1.value.enumerated() {
    5. let imageToDisplay: URL? = URL(string: allQuestionPictureURL + picture2)
    6. let questionImageView: UIImageView = UIImageView(image:nil)
    7. if let imageUrl = imageToDisplay {
    8. questionImageView.sd_setImage(with: imageUrl)
    9. }
    10. questionImageView.contentMode = .scaleAspectFit
    11. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    12. contentWidth += view.frame.width
    13. VotingScrollView.addSubview(questionImageView)
    14. questionImageView.frame = CGRect(x: xCoordinate, y: CGFloat(0) , width: CGFloat(view.frame.size.width), height: CGFloat(view.frame.size.height)/2)
    15. }
    16. }
    17. }
    18. VotingScrollView.contentSize = CGSize(width: contentWidth, height: CGFloat(view.frame.size.height)/2)
    Alles anzeigen

    Leider habe ich das Problem das die Bilder nicht den kompletten Platz der VotingScrollView ausnutzen. So habe ich beim iPhone 8 Plus immer links und rechts noch etwas weißen Rand und beim iPhone 8 das Problem das die Bilder einmal den Rand aufweisen und nicht bis ganz nach unten gehen.

    Ich habe also wie du gesagt hast nach einer autoresizing Möglichkeit geschaut und auch was gefunden. Jedoch will das bei mir nicht so richtig klappen.
    Ich hätte gedacht das ganze in etwa so zu lösen wie im folgenden zu sehen nur bin ich auf xCoordinate angewiesen da sich darin der Index der Bilder befindet:

    Quellcode

    1. questionImageView.autoresizingMask = [.flexibleRightMargin, .flexibleLeftMargin, .flexibleBottomMargin]

    Jetzt also die Frage wie ich es hinbekomme das die questionImageView sich an die VotingScrollView hält und sämtlichen Platz ausfüllt ?

    PS: Bilder die bspw. von der Größe her nicht ganz auf die der VotingScrollView passen sollen ja berechtigterweise an bestimmter Stelle einen weißen Rand bekommen, die die aber vom Format her passen sollen alles ausfüllen.
  • Also hab jetzt wie du gesagt hast die viewDidLayoutSubviews() Funktion verwendet weil wie du sagtest ich sonst immer das Problem hast das der Container nicht rechtzeitig auf die neue Größe geupdatet wurde. Jetzt funktioniert eigentlich alles genau so wie ich es mir vorstelle wenn da nicht ein Problem wäre wegen dem ich mir gerade den Kopf zerbreche. Hier aber erstmal der aktuelle Code:


    Quellcode

    1. override func viewDidLayoutSubviews() {
    2. for picture in questionToPicture {
    3. for picture1 in picture where picture1.key == questionLabelText {
    4. VotePageControl.numberOfPages = picture1.value.count
    5. for (index,picture2) in picture1.value.enumerated() {
    6. let imageToDisplay: URL? = URL(string: allQuestionPictureURL + picture2)
    7. let questionImageView: UIImageView = UIImageView(image:nil)
    8. if let imageUrl = imageToDisplay {
    9. questionImageView.sd_setImage(with: imageUrl)
    10. }
    11. questionImageView.contentMode = .scaleAspectFit
    12. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    13. print(CGFloat(index) * self.view.bounds.size.width)
    14. contentWidth += view.frame.width
    15. VotingScrollView.addSubview(questionImageView)
    16. questionImageView.frame = CGRect(x: xCoordinate, y: CGFloat(0), width: VotingScrollView.frame.size.width, height: VotingScrollView.frame.size.height)
    17. }
    18. }
    19. }
    20. VotingScrollView.contentSize = CGSize(width: contentWidth, height: VotingScrollView.frame.size.height)
    21. }
    Alles anzeigen
    Normalerweise wird durch den Index mit angegeben wie viele Bilder sich in der ScrollView befinden und festgelegt wie oft ich also nach links oder rechts swipen kann. Sagen wir ich habe 3 Bilder dann kann ich auch nur so oft swipen wie halt Bilder vorhanden sind.
    Jetzt habe ich aber das Problem das wohl durch die viewDidLayoutSubviews() der Code zwei mal ausgeführt wird. In der Konsole werden nun statt 3 Bilder, 6 angezeigt. Genau die drei die richtigerweise in der ScrollView sind werden jetzt noch zusätzlich ein weiteres mal angehangen wodurch ich sozusagen ins leere swipen kann...

    Wie kann ich das verhindern und festlegen das der Code nur einmal ausgeführt wird ?
  • viewDidLayoutSubviews() wird beliebig oft aufgerufen. Wie der Name vermuten lässt, jedes mal, wenn sich das Layout der Subviews verändert hat.

    Du kannst die questionImageViews weiterhin in viewDidLoad() erzeugen und dem ScrollView hinzufügen. In viewDidLayoutSubviews() passt Du dann nur noch die Frames der questionImageViews passend zum ScrollView, sowie die contentSize des ScrollViews an.

    Bei den questionImageViews kannst Du beim Erzeuge den Index als tag setzen. Damit kannst Du dann in viewDidLayoutSubviews() in einer Schleife per viewWithTag(_:) die questionImageViews in dem ScrollView ermitteln und den Frame passend zum Index/Tag anpassen.
  • MCDan schrieb:


    Bei den questionImageViews kannst Du beim Erzeuge den Index als tag setzen. Damit kannst Du dann in viewDidLayoutSubviews() in einer Schleife per viewWithTag(_:) die questionImageViews in dem ScrollView ermitteln und den Frame passend zum Index/Tag anpassen.
    Ich habe das gerade mal eben ausprobiert doch ich habe ziemliche Probleme damit. Das Problem was ich hatte war in der Schleife über viewWithTag die questionImageViews anzusprechen da diese ja nur in der viewDidLoad erzeugt wurde und nicht global verfügbar ist. Also habe ich nach einer anderen Möglichkeit gesucht und bin dabei auf folgendes gestoßen. Mein Code scheint nun so aus und mich würden eure Meinungen dazu interessieren :)


    Quellcode

    1. override func viewDidLayoutSubviews() {
    2. if firstLayoutSubviewsTime {
    3. firstLayoutSubviewsTime = false
    4. for picture in questionToPicture {
    5. for picture1 in picture where picture1.key == questionLabelText {
    6. VotePageControl.numberOfPages = picture1.value.count
    7. for (index,picture2) in picture1.value.enumerated() {
    8. let imageToDisplay: URL? = URL(string: allQuestionPictureURL + picture2)
    9. let questionImageView: UIImageView = UIImageView(image:nil)
    10. if let imageUrl = imageToDisplay {
    11. questionImageView.sd_setImage(with: imageUrl)
    12. }
    13. questionImageView.contentMode = .scaleAspectFit
    14. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    15. print(CGFloat(index) * self.view.bounds.size.width)
    16. contentWidth += view.frame.width
    17. VotingScrollView.addSubview(questionImageView)
    18. questionImageView.frame = CGRect(x: xCoordinate, y: CGFloat(0), width: VotingScrollView.frame.size.width, height: VotingScrollView.frame.size.height)
    19. }
    20. }
    21. }
    22. }
    23. VotingScrollView.contentSize = CGSize(width: contentWidth, height: VotingScrollView.frame.size.height)
    24. }
    Alles anzeigen
    Es gibt eine Variable firstLayoutSubviewsTime die auf true gesetzt ist und beim ersten Aufruf auf false gesetzt wird. Dadurch verhindere ich das die viewDidLayoutSubviews zwei mal ausgeführt wird. Funktioniert ist aber unter Umständen nicht die schönste Art und Weise.

    Deswegen würde es mich sehr interessieren wenn du @MCDan mir vielleicht nochmal erklären könntest wie genau ich dann über viewWithTag den Zugriff auf die questionImageViews bekommen kann bzw. wie ich diese in der VotingScrollView ermitteln soll :)
  • Den Code in viewDidLoad() kannst Du so lassen. Einzige Ergänzung wäre z.B. ein

    Quellcode

    1. questionImageView.tag = 1000 + index
    nach dem Erzeugen des questionImageViews.

    In viewDidLayoutSubviews() könnte dann so etwas stehen (ohne Gewähr auf korrekte Syntax ;) )

    Quellcode

    1. var index = 0
    2. var contentWidth = 0
    3. var questionImageView: UIImageView? = nil
    4. repeat {
    5. questionImageView = VotingScrollView.viewWithTag(1000 + index)
    6. if questionImageView {
    7. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    8. contentWidth += view.frame.width
    9. questionImageView.frame = CGRect(x: xCoordinate, y: CGFloat(0) , width: CGFloat(view.frame.size.width), height: CGFloat(view.frame.size.height)/2)
    10. }
    11. index += 1
    12. } while questionImageView
    13. VotingScrollView.contentSize = CGSize(width: contentWidth, height: CGFloat(view.frame.size.height)/2)
    Alles anzeigen
  • Hey ich habe mal wieder ein komisches Problem mit der viedDidLayoutSubviews Funktion. Ich habe bei mir nämlich eine ScrollView hinzugefügt damit ich den Sichtbereich der App einfach ein wenig vergrößere.
    Das ich dadurch aber auch ein Problem mit der skalierung mancher Elemente haben würde wusste ich nicht.

    Am Code selbst habe ich nicht mal wirklich was gemacht, mir sind aber ein paar interessante Dinge aufgefallen. Doch hier erstmal mein Code:

    Quellcode

    1. override func viewDidLayoutSubviews() {
    2. if firstLayoutSubviewsTime {
    3. firstLayoutSubviewsTime = false
    4. // verhindert das die View zwei mal geladen wird
    5. for picture in questionToPicture {
    6. for picture1 in picture where picture1.key == questionLabelText {
    7. VotePageControl.numberOfPages = picture1.value.count
    8. for (index,picture2) in picture1.value.enumerated() {
    9. let imageToDisplay: URL? = URL(string: allQuestionPictureURL + picture2)
    10. let questionImageView: UIImageView = UIImageView(image:nil)
    11. if let imageUrl = imageToDisplay {
    12. questionImageView.sd_setImage(with: imageUrl)
    13. }
    14. questionImageView.contentMode = .scaleAspectFit
    15. let xCoordinate = CGFloat(index) * self.view.bounds.size.width
    16. print(CGFloat(index) * self.view.bounds.size.width)
    17. contentWidth += view.frame.width
    18. VotingScrollView.addSubview(questionImageView)
    19. questionImageView.frame = CGRect(x: xCoordinate, y: CGFloat(0), width: VotingScrollView.frame.size.width, height: VotingScrollView.frame.size.height)
    20. // Delegate, stellt auf erstes Bild falls geliked wird ohne scroll
    21. shownPictureString = picture1.value[0]
    22. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(pictureDoubleTapped(recognizer:)))
    23. tapGestureRecognizer.numberOfTapsRequired = 2
    24. VotingScrollView.addGestureRecognizer(tapGestureRecognizer)
    25. }
    26. }
    27. }
    28. }
    29. VotingScrollView.contentSize = CGSize(width: contentWidth, height: VotingScrollView.frame.size.height)
    30. }
    Alles anzeigen
    Okay also im wesentlichen geht es mir hier um questionImageView.frame. Das Problem ist dass das Bild welches sich in der VotingScrollView befindet, sich nicht auf dessen Größe anpasst. Würde ich jedoch für width und height genaue Pixelangaben machen wie bspw. 414, dann funktioniert alles wie gewünscht.

    So wie der Code jetzt ist würde es außerdem noch dann funktionieren wenn man firstLayoutSubviewsTime raus nehmen würde. Der Haken dabei wäre nur das die viewDidLayout Funktion dann zwei mal ausgeführt wird...

    Irgendwie verwirrt mich das ganze da es ja eigentlich alles funktionierte bevor ich das alles in eine neue ScrollView geschmissen habe. Jetzt ist meine Frage, wieso das ganze dann funktioniert wenn man genaue Pixelangaben macht aber nicht dann wenn man die width und height Angaben der VotingScrollView wählt und wie ich das Problem beheben kann ?