Search code examples
iosswiftuiimageviewnslayoutconstraint

How to layout UIImageView with text right to it programatically for en and ar locale?


I am trying to layout image view with label right to it. I got the layout to work in English locale, but it gives the following constraints error. It fails to render in Arabic mode, where the UIImageView is not switching its position to right.

Screenshot in English locale.

image view en

2019-05-28 17:32:09.889997+0530 App[33841:7264068] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x60000039c640 h=--& v=--& UIImageView:0x7fab2cf7c3c0.midX == 38   (active)>",
    "<NSLayoutConstraint:0x6000003a63f0 UIImageView:0x7fab2cf7c3c0.width == 38   (active)>",
    "<NSLayoutConstraint:0x6000003a6350 H:|-(0)-[UIImageView:0x7fab2cf7c3c0]   (active, names: '|':UIView:0x7fab2cf73120 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000003a63f0 UIImageView:0x7fab2cf7c3c0.width == 38   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

// UI.swift
static func imageView(_ imageName: String) -> UIImageView {
    let iv = UIImageView(image: UIImage(named: imageName))
    iv.translatesAutoresizingMaskIntoConstraints = true
    iv.clipsToBounds = true
    return iv
}
// ProfileViewController.swift
lazy var profileImageView: UIImageView = {
    let iv = UI.imageView("profile-placeholder")
    return iv
}()
lazy var welcomeLbl: UILabel = {
    let lbl = UI.label(text: UI.lmsg("Welcome"))
    lbl.font = UIFont(name: "Roboto", size: 16)
    return lbl
}()
lazy var nameLbl: UILabel = {
    let lbl = UI.label(text: "username placeholder")
    lbl.font = UIFont(name: "Roboto-Bold", size: 16)
    return lbl
}()
lazy var profileHeaderView: UIView = {
    let v = UI.view()
    v.translatesAutoresizingMaskIntoConstraints = false
    let lblView = UI.view()
    lblView.translatesAutoresizingMaskIntoConstraints = false
    lblView.addSubview(welcomeLbl)
    lblView.addSubview(nameLbl)
    lblView.layer.borderColor = UIColor.red.cgColor
    lblView.layer.borderWidth = 1.0
    // label view
    NSLayoutConstraint.activate([
        welcomeLbl.topAnchor.constraint(equalTo: lblView.topAnchor, constant: 0),
        welcomeLbl.leadingAnchor.constraint(equalTo: lblView.leadingAnchor, constant: 0),
        welcomeLbl.trailingAnchor.constraint(equalTo: lblView.trailingAnchor, constant: 0),
        welcomeLbl.bottomAnchor.constraint(equalTo: nameLbl.topAnchor, constant: -8)
    ])
    NSLayoutConstraint.activate([
        nameLbl.topAnchor.constraint(equalTo: welcomeLbl.bottomAnchor, constant: 8),
        nameLbl.leadingAnchor.constraint(equalTo: lblView.leadingAnchor, constant: 0),
        nameLbl.trailingAnchor.constraint(equalTo: lblView.trailingAnchor, constant: 0),
        nameLbl.bottomAnchor.constraint(equalTo: lblView.bottomAnchor, constant: 0)
    ])
    v.addSubview(profileImageView)
    v.addSubview(lblView)
    NSLayoutConstraint.activate([
        profileImageView.centerYAnchor.constraint(equalTo: v.centerYAnchor, constant: 0),
        profileImageView.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 0),
        profileImageView.trailingAnchor.constraint(equalTo: lblView.leadingAnchor, constant: -8),
        profileImageView.widthAnchor.constraint(equalToConstant: 38)
    ])
    NSLayoutConstraint.activate([
        lblView.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor, constant: 0),
        lblView.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 8),
        lblView.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8)
    ])
    v.layer.borderColor = #colorLiteral(red: 0.2745098174, green: 0.4862745106, blue: 0.1411764771, alpha: 1)
    v.layer.borderWidth = 1.0
    return v
}()

I am then adding the profileHeaderView in a stackView. How to fix this?


Solution

  • It would be easier to use nested UIStackViews here so that constraints are less of an issue. What you want is something like

    .____________________________.
    |.________. ._______________.|
    ||        | |._____________.||
    ||        | ||Welcome      |||
    ||        | |'-------------'||
    ||        | |._____________.||
    ||        | ||username     |||
    ||        | |'-------------'||
    |'--------' '---------------'|
    '----------------------------'
    
    

    where the labels are in a vertical UIStackView, then that stack view and the image are in a horizontal one.

    At runtime, when the user switches to a right-to-left locale, remove the image and reinsert it at the end of the outer stack view. Do the opposite when switching back to left-to-right.

    The only constraints that should be needed here are the image's dimensions and the outermost stack view's position relative to its parent. The stack views' spacing and alignment properties take the place of constraints for the nested items.