Как оценить предпочтительный размер UIView, когда одно из дочерних представлений использует ограничение отношения?

Я понял, что не понимаю AutoLayout.

Я хочу измерить требуемую высоту представления с учетом постоянной ширины.

Это мой TestViewTwo.xib

Как оценить предпочтительный размер UIView, когда одно из дочерних представлений использует ограничение отношения?

TestViewTwo.swift

import UIKit

class TestViewTwo: UIView {
    @IBOutlet weak var imageView: UIImageView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        let nib = Bundle.main.loadNibNamed("TestViewTwo", owner: self, options: nil)
        let view = nib!.first as! UIView
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
        view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
    }
}

Контроллер тестирования

import Foundation
import UIKit

class TestControllerTwo : UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let testView = TestViewTwo()
        
        let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500))
        
        print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")
    }
}

На выходе

Estimated size: (100.0, 500.0), imageView.frame: (0.0, 0.0, 414.0, 621.0)

Не понимаю, почему расчетная ширина 100? Откуда это взялось? Почему ориентировочная высота 500, а не 300 (200x1,5)? Я также не понимаю, почему установлен фрейм imageView и почему такие значения

Пожалуйста, помогите мне понять, что я делаю не так.

Я хочу получить приблизительный размер = 200x300

Обновлять:

Я полагаю, что я здесь делаю что-то в корне не так. Я использую не соотношение.

Когда я устанавливаю постоянную ширину и высоту просмотра изображения

Как оценить предпочтительный размер UIView, когда одно из дочерних представлений использует ограничение отношения?

я получил

Estimated size: (200.0, 500.0), imageView.frame: (0.0, 0.0, 200.0, 300.0)

Когда я устанавливаю только постоянную высоту

Как оценить предпочтительный размер UIView, когда одно из дочерних представлений использует ограничение отношения?

я получил

Estimated size: (100.0, 500.0), imageView.frame: (0.0, 0.0, 414.0, 300.0)

Что не так в моем макете / коде, так что я не могу получить EstimatedSize = 200x300?

Прежде чем перейти к проблеме соотношения, давайте разберемся с постоянными размерами.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Распространенной проблемой systemLayoutSizeFitting является некорректная работа при использовании соотношения сторон. Он использует intrinsicContentSize для установки своей высоты, поэтому вы получите другое значение. Чтобы решить эту проблему, вам необходимо удалить соотношение сторон и явно указать высоту. В коде это будет выглядеть так:

imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5).isActive = true

Также [см.] [1] аналогичный ответ.

Обновлять:

После того, как вы предоставили немного больше информации, я обнаружил несколько проблем:

  1. Вы получаете 500 за высоту, потому что пытаетесь оценить высоту TestViewTwo, а не фактическую высоту UIImageView. TestViewTwo будет иметь высоту 500, поскольку у него нет нижнего якоря, и он будет растягиваться, чтобы заполнить доступное пространство.
  2. Для оценки лучше использовать systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority:).

На основании документации:

Use this method when you want to prioritize the view's constraints when determining the best possible size of the view. This method does not actually change the size of the view.

Применяя эти изменения, я подготовил для вас демоверсию, чтобы вы могли проверить ее и поэкспериментировать. Я назвал View, в котором находится UIImageView, ImageView:

class ImageView: UIView {
    let imageView = UIImageView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        imageView.image = UIImage(named: "image1")
        addSubview(imageView)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
        imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
        imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
        imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        imageView.heightAnchor.constraint(equalToConstant: 300).isActive = true
        backgroundColor = .blue
    }
}

TestViewTwo я назвал ImageViewHolder:

class ImageViewHolder: UIView {
    let view = ImageView(frame: .zero)

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
        view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
        // Bottom anchor should not be set here, as the view has explicitly defined it's height
        backgroundColor = .red
    }
}

А вот ViewController, где вы можете его протестировать:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // I gave a frame to this so you can see the actual layout
        // But the estimation logic also works if you pass here .zero a
        let imageViewHollder = ImageViewHolder(frame: CGRect(x: 0, y: 100, width: 200, height: 500)) 

        let estimatedSize = imageViewHollder.view.systemLayoutSizeFitting(CGSize(width: 200, height: 500), withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultHigh)
        print("Estimated size: \(estimatedSize)")
        
        // Just to see the actual layout
        view.addSubview(imageViewHollder)
    }
}

Обратите внимание, что при печати примерного размера imageView:

let estimatedSize = imageViewHollder.view.systemLayoutSizeFitting(CGSize(width: 200, height: 500), withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultHigh)
print("Estimated size: \(estimatedSize)")

Ты получаешь:

// Estimated size: (200.0, 300.0)

Но если вы напечатаете приблизительный размер представления, в котором оно находится

let estimatedSize = imageViewHollder.systemLayoutSizeFitting(CGSize(width: 200, height: 500), withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultHigh)
print("Estimated size: \(estimatedSize)")

Ты получаешь

Estimated size: (200.0, 500.0)

Это связано с тем, что, как объяснялось выше, представление держателя будет пытаться заполнить доступное пространство, поскольку оно не имеет нижней привязки.

Я проверил ваше предложение, оно не сработало. Поэтому я решил сделать шаг назад и попытаться оценить размер представления с постоянными размерами. Это тоже не работает. Итак, я чувствую, что в моем макете / коде что-то принципиально не так, и обновил свой вопрос

ievgen 31.03.2021 12:32

@ievgen посмотреть обновление

πter 31.03.2021 13:34
Ответ принят как подходящий

Вам определенно нужно ограничить нижнюю часть изображения до нижней части его супервизора ...

Дайте нижнее ограничение Priority: High (750)

Затем, когда вы хотите узнать предполагаемое значение Высота на основе данного прямоугольника:

    let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500),
                                     withHorizontalFittingPriority: .defaultHigh,
                                     verticalFittingPriority: .defaultLow)

    print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")
    
    // output: Estimated size: (200.0, 300.0), imageView.frame: (0.0, 0.0, 197.0, 295.5)

Если вы хотите узнать расчетное значение Ширина для данного прямоугольника:

    let estimatedSize = testView.systemLayoutSizeFitting(CGSize(width: 200, height: 500),
                                     withHorizontalFittingPriority: .defaultLow,
                                     verticalFittingPriority: .defaultHigh)
    
    print("Estimated size: \(estimatedSize), imageView.frame: \(testView.imageView.frame)")

    // output: Estimated size: (333.5, 500.0), imageView.frame: (0.0, 0.0, 197.0, 295.5)

Обратите внимание, что для imageView.frame еще будет установлен НЕТ, поэтому он будет оценивать тот размер, который у вас есть в IB.

Также обратите внимание, что мы даем представлению изображения ограничение Bottom с приоритетом менее чем требуется. Это позволяет избежать предупреждений IB, когда размер кадра просмотра не соответствует точному соотношению 1:1.5, и позволяет избежать сообщений с предупреждениями / ошибками автоматической компоновки во время выполнения.


Вот источник XIB:

<?xml version = "1.0" encoding = "UTF-8"?>
<document type = "com.apple.InterfaceBuilder3.CocoaTouch.XIB" version = "3.0" toolsVersion = "17701" targetRuntime = "iOS.CocoaTouch" propertyAccessControl = "none" useAutolayout = "YES" useTraitCollections = "YES" colorMatched = "YES">
    <device id = "retina4_0" orientation = "portrait" appearance = "light"/>
    <dependencies>
        <deployment identifier = "iOS"/>
        <plugIn identifier = "com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version = "17703"/>
        <capability name = "documents saved in the Xcode 8 format" minToolsVersion = "8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier = "IBFilesOwner" id = "-1" userLabel = "File's Owner" customClass = "TestViewTwo" customModule = "DrawingTutorial" customModuleProvider = "target">
            <connections>
                <outlet property = "imageView" destination = "QgA-Qr-3jM" id = "MGu-3W-9i4"/>
            </connections>
        </placeholder>
        <placeholder placeholderIdentifier = "IBFirstResponder" id = "-2" customClass = "UIResponder"/>
        <view contentMode = "scaleToFill" id = "iN0-l3-epB">
            <rect key = "frame" x = "0.0" y = "0.0" width = "197" height = "391"/>
            <autoresizingMask key = "autoresizingMask" widthSizable = "YES" heightSizable = "YES"/>
            <subviews>
                <imageView clipsSubviews = "YES" userInteractionEnabled = "NO" contentMode = "scaleToFill" horizontalHuggingPriority = "251" verticalHuggingPriority = "251" translatesAutoresizingMaskIntoConstraints = "NO" id = "QgA-Qr-3jM">
                    <rect key = "frame" x = "0.0" y = "0.0" width = "197" height = "295.5"/>
                    <color key = "backgroundColor" red = "0.99998801950000005" green = "0.62141335009999998" blue = "0.00022043679199999999" alpha = "1" colorSpace = "custom" customColorSpace = "sRGB"/>
                    <constraints>
                        <constraint firstAttribute = "width" secondItem = "QgA-Qr-3jM" secondAttribute = "height" multiplier = "1:1.5" id = "CpW-r1-rJA"/>
                    </constraints>
                </imageView>
            </subviews>
            <color key = "backgroundColor" red = "0.45009386540000001" green = "0.98132258650000004" blue = "0.4743030667" alpha = "1" colorSpace = "custom" customColorSpace = "sRGB"/>
            <constraints>
                <constraint firstItem = "QgA-Qr-3jM" firstAttribute = "leading" secondItem = "iN0-l3-epB" secondAttribute = "leading" id = "3As-tz-AZL"/>
                <constraint firstAttribute = "trailing" secondItem = "QgA-Qr-3jM" secondAttribute = "trailing" id = "4Q2-dC-O75"/>
                <constraint firstItem = "QgA-Qr-3jM" firstAttribute = "top" secondItem = "iN0-l3-epB" secondAttribute = "top" id = "xJ2-05-m7l"/>
                <constraint firstAttribute = "bottom" secondItem = "QgA-Qr-3jM" secondAttribute = "bottom" priority = "750" id = "xy9-yL-2gg"/>
            </constraints>
            <freeformSimulatedSizeMetrics key = "simulatedDestinationMetrics"/>
            <point key = "canvasLocation" x = "109.6875" y = "126.23239436619718"/>
        </view>
    </objects>
</document>

И примеры классов для демонстрации:

class TestControllerTwo : UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let testView = TestViewTwo()
        
        // withHorizontalFittingPriority: .defaultHigh
        // verticalFittingPriority: .defaultLow
        //  gives priority to the WIDTH
        //  returns a size based on fitting the Target WIDTH
        let estimatedSizeW = testView.systemLayoutSizeFitting(
            CGSize(width: 200, height: 500),
            withHorizontalFittingPriority: .defaultHigh,
            verticalFittingPriority: .defaultLow)
        
        print("Width Priority Estimated size: \(estimatedSizeW)",
            "imageView.frame: \(testView.imageView.frame)")

        // withHorizontalFittingPriority: .defaultLow
        // verticalFittingPriority: .defaultHigh
        //  gives priority to the HEIGHT
        //  returns a size based on fitting the Target HEIGHT
        let estimatedSizeH = testView.systemLayoutSizeFitting(
            CGSize(width: 200, height: 500),
            withHorizontalFittingPriority: .defaultLow,
            verticalFittingPriority: .defaultHigh)
        
        print("Height Priority Estimated size: \(estimatedSizeH)",
            "imageView.frame: \(testView.imageView.frame)")
        
    }

}

class TestViewTwo: UIView {
    @IBOutlet weak var imageView: UIImageView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        let nib = Bundle.main.loadNibNamed("TestViewTwo", owner: self, options: nil)
        let view = nib!.first as! UIView
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
        view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
    }
}

Не могли бы вы показать или сказать, что находится внутри свернутых ограничений в представлении изображения?

ievgen 01.04.2021 06:27

@ievgen - извините, я обычно дважды и трижды проверяю, все ли развернуто в структуре документа. См. Правки к моему ответу ... Я заменил крышку экрана и включил исходный код XIB, а также пример кода для классов (класс TestViewTwo - это именно то, что вы разместили в своем вопросе).

DonMag 01.04.2021 14:58

Другие вопросы по теме