Autolayout-conform UILabel с вертикальным текстом (objC или Swift)?

Как мне создать UIView/UILabel с вертикальным потоком текста, который будет выглядеть как красный вид этого примера экрана?

Autolayout-conform UILabel с вертикальным текстом (objC или Swift)?

Я читал о view.transform = CGAffineTransform(... который позволяет легко вращать, НО это нарушит ограничения автоматического макета.

Я бы с удовольствием использовал стороннюю библиотеку, но не могу найти.

Ваш вопрос не ясен. Попробуйте подробно описать, что вы хотите сделать, и показать, что у вас есть пытался, что не дало вам желаемых результатов (с изображениями, если это поможет прояснить).

DonMag 23.03.2022 15:37

ты прав. "вертикаль" может быть неоднозначным. Пример экрана должен прояснить проблему.

Mick 25.03.2022 23:41

Хммм… Я помню, как некоторое время назад публиковал решение для этого. Я должен быть в состоянии найти его завтра.

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

Ответы 1

Ответ принят как подходящий

Как отмечено в Apple документы:

In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.

Таким образом, чтобы преобразованные представления «играли хорошо» с автоматическим макетом, нам нужно, по сути, указать ограничениям использовать противоположную ось.

Например, если мы вставляем UILabel в UIView и поворачиваем метку на 90 градусов, мы хотим ограничить Ширина представления «контейнера» значением Высота метки, а его Высота — значением Ширина метки.

Вот пример подкласса просмотра VerticalLabelView:

class VerticalLabelView: UIView {
    
    public var numberOfLines: Int = 1 {
        didSet {
            label.numberOfLines = numberOfLines
        }
    }
    public var text: String = "" {
        didSet {
            label.text = text
        }
    }
    
    // vertical and horizontal "padding"
    //  defaults to 16-ps (8-pts on each side)
    public var vPad: CGFloat = 16.0 {
        didSet {
            h.constant = vPad
        }
    }
    public var hPad: CGFloat = 16.0 {
        didSet {
            w.constant = hPad
        }
    }
    
    // because the label is rotated, we need to swap the axis
    override func setContentHuggingPriority(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) {
        label.setContentHuggingPriority(priority, for: axis == .horizontal ? .vertical : .horizontal)
    }
    
    // this is just for development
    //  show/hide border of label
    public var showBorder: Bool = false {
        didSet {
            label.layer.borderWidth = showBorder ? 1 : 0
            label.layer.borderColor = showBorder ? UIColor.red.cgColor : UIColor.clear.cgColor
        }
    }
    
    public let label = UILabel()
    
    private var w: NSLayoutConstraint!
    private var h: NSLayoutConstraint!
    private var mh: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        addSubview(label)
        label.backgroundColor = .clear
        
        label.translatesAutoresizingMaskIntoConstraints = false
        
        // rotate 90-degrees
        let angle = .pi * 0.5
        label.transform = CGAffineTransform(rotationAngle: angle)
        
        // so we can change the "padding" dynamically
        w = self.widthAnchor.constraint(equalTo: label.heightAnchor, constant: hPad)
        h = self.heightAnchor.constraint(equalTo: label.widthAnchor, constant: vPad)
        
        NSLayoutConstraint.activate([
            
            label.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            w, h,
            
        ])
        
    }
    
}

Я добавил несколько свойств, чтобы представление можно было рассматривать как метку, поэтому мы можем сделать:

let v = VerticalLabelView()

// "pass-through" properties
v.text = "Some text which will be put into the label."
v.numberOfLines = 0

// directly setting properties
v.label.textColor = .red

Конечно, это можно расширить, чтобы «пропустить» все свойства метки, которые нам нужно использовать, поэтому нам не нужно будет напрямую ссылаться на .label.

Этот VerticalLabelView теперь можно использовать так же, как обычный UILabel.

Вот два примера — они оба используют этот BaseVC для настройки представлений:

class BaseVC: UIViewController {
    
    let greenView: UIView = {
        let v = UIView()
        v.backgroundColor = .green
        return v
    }()
    let normalLabel: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        return v
    }()
    
    let lYellow: VerticalLabelView = {
        let v = VerticalLabelView()
        v.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 0.5, alpha: 1.0)
        v.numberOfLines = 0
        return v
    }()
    
    let lRed: VerticalLabelView = {
        let v = VerticalLabelView()
        v.backgroundColor = UIColor(red: 1.0, green: 0.5, blue: 0.5, alpha: 1.0)
        v.numberOfLines = 0
        return v
    }()
    
    let lBlue: VerticalLabelView = {
        let v = VerticalLabelView()
        v.backgroundColor = UIColor(red: 0.3, green: 0.8, blue: 1.0, alpha: 1.0)
        v.numberOfLines = 1
        return v
    }()
    
    let container: UIView = {
        let v = UIView()
        v.backgroundColor = .systemYellow
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let strs: [String] = [
            "Multiline Vertical Text",
            "Vertical Text",
            "Overflow Vertical Text",
        ]
        
        // default UILabel
        normalLabel.text = "Regular UILabel wrapping text"
        // add the normal label to the green view
        greenView.addSubview(normalLabel)
        
        // set text of vertical labels
        for (s, v) in zip(strs, [lYellow, lRed, lBlue]) {
            v.text = s
        }
        
        [container, greenView, normalLabel, lYellow, lRed, lBlue].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // add greenView to the container
        container.addSubview(greenView)
        
        // add container to self's view
        view.addSubview(container)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain container Top and CenterX
            container.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            container.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            // comment next line to allow container subviews to set the height
            container.heightAnchor.constraint(equalToConstant: 260.0),
            
            // comment next line to allow container subviews to set the width
            container.widthAnchor.constraint(equalToConstant: 160.0),
            
            // green view at Top, stretched full width
            greenView.topAnchor.constraint(equalTo: container.topAnchor, constant: 0.0),
            greenView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0.0),
            greenView.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),

            // constrain normal label in green view
            //  with 8-pts "padding" on all 4 sides
            normalLabel.topAnchor.constraint(equalTo: greenView.topAnchor, constant: 8.0),
            normalLabel.leadingAnchor.constraint(equalTo: greenView.leadingAnchor, constant: 8.0),
            normalLabel.trailingAnchor.constraint(equalTo: greenView.trailingAnchor, constant: -8.0),
            normalLabel.bottomAnchor.constraint(equalTo: greenView.bottomAnchor, constant: -8.0),
            
        ])
    }
    
}

Первый пример — SubviewsExampleVC — добавляет каждое как подпредставление, а затем мы добавляем ограничения между представлениями:

class SubviewsExampleVC: BaseVC {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // add vertical labels to the container
        [lYellow, lRed, lBlue].forEach { v in
            container.addSubview(v)
        }

        NSLayoutConstraint.activate([
            
            // yellow label constrained to Bottom of green view
            lYellow.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: 0.0),
            // Leading to container Leading
            lYellow.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0.0),
            
            // red label constrained to Bottom of green view
            lRed.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: 0.0),
            // Leading to yellow label Trailing
            lRed.leadingAnchor.constraint(equalTo: lYellow.trailingAnchor, constant: 0.0),
            
            // blue label constrained to Bottom of green view
            lBlue.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: 0.0),
            // Leading to red label Trailing
            lBlue.leadingAnchor.constraint(equalTo: lRed.trailingAnchor, constant: 0.0),
            
            // if we want the labels to fill the container width
            //  blue label Trailing constrained to container Trailing
            lBlue.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),
            
            // using constraints to set the vertical label heights
            lYellow.heightAnchor.constraint(equalToConstant: 132.0),
            lRed.heightAnchor.constraint(equalTo: lYellow.heightAnchor),
            lBlue.heightAnchor.constraint(equalTo: lYellow.heightAnchor),
            
        ])
        
        // as always, we need to control which view(s)
        //  hug their content

        // so, for example, if we want the Yellow label to "stretch" horizontally
        lRed.setContentHuggingPriority(.required, for: .horizontal)
        lBlue.setContentHuggingPriority(.required, for: .horizontal)
        
        // or, for example, if we want the Red label to "stretch" horizontally
        //lYellow.setContentHuggingPriority(.required, for: .horizontal)
        //lBlue.setContentHuggingPriority(.required, for: .horizontal)

    }

}

Второй пример = StackviewExampleVC — добавляет каждое как упорядоченное подпредставление UIStackView:

class StackviewExampleVC: BaseVC {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // horizontal stack view
        let stackView = UIStackView()
        
        // add vertical labels to the stack view
        [lYellow, lRed, lBlue].forEach { v in
            stackView.addArrangedSubview(v)
        }
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        // add stack view to container
        container.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            
            // constrain stack view Top to green view Bottom
            stackView.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: 0.0),

            // Leading / Trailing to container Leading / Trailing
            stackView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),
            
            // stack view height
            stackView.heightAnchor.constraint(equalToConstant: 132.0),
            
        ])

        // as always, we need to control which view(s)
        //  hug their content
        // so, for example, if we want the Yellow label to "stretch" horizontally
        lRed.setContentHuggingPriority(.required, for: .horizontal)
        lBlue.setContentHuggingPriority(.required, for: .horizontal)
        
        // or, for example, if we want the Red label to "stretch" horizontally
        //lYellow.setContentHuggingPriority(.required, for: .horizontal)
        //lBlue.setContentHuggingPriority(.required, for: .horizontal)
        
    }
    
}

Оба примера производят этот вывод:

Обратите внимание: это Только пример кода — он не предназначен и не должен считаться Производство готово.

Большое спасибо! Великолепно! Работает из коробки! Это гораздо более чистое решение, чем я надеялся! :)

Mick 28.03.2022 16:13

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