简体   繁体   中英

How to put a constraint between a UILabel and two UIImageView?

I'm trying to make a horizontal UIStackView with 2 images and a label:

  1. The first image ico_qol should be anchored to the left side of the view.
  2. The second image ico_edit should be anchored to the right side of the view.
  3. The label should be anchored in between the two images.
  4. The view should be sized to the height of the images which are the same. It should span the entire screen width.

What I'm trying to achieve is something like this (selected elements in blue):

在此处输入图片说明

Here is my code:

   public partial class ViewController : UIViewController
    {
        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            UIStackView header = new UIStackView();
            header.Axis = UILayoutConstraintAxis.Horizontal;
            View.AddSubview(header);

            //< color name = "healthy_green" >#409900</color>
            var healthy_green = new UIColor(red: 0.25f, green: 0.60f, blue: 0.00f, alpha: 1.00f);
            //< color name = "optimistic_orange" >#FF5F00</color>
            var optimistic_orange = new UIColor(red: 1.00f, green: 0.37f, blue: 0.00f, alpha: 1.00f);

            // Enable Auto Layout
            header.TranslatesAutoresizingMaskIntoConstraints = false;
            var ic_qol_image = UIImage.FromBundle("ic_qol");
            ic_qol_image.ApplyTintColor(healthy_green);
            var ic_qol = new UIImageView(ic_qol_image);
            ic_qol.SizeToImage();

            var qol_title = new UILabel { TranslatesAutoresizingMaskIntoConstraints = false, Text = @"Quality of Life" };

            var ic_edit_image = UIImage.FromBundle("ic_edit");
            ic_edit_image.ApplyTintColor(optimistic_orange);
            var ic_edit = new UIImageView(ic_edit_image);
            ic_edit.SizeToImage();

            var qolHeaderViews = new UIView[] { ic_qol, qol_title, ic_edit };
            header.AddSubviews(qolHeaderViews);

            header.Anchor(top: View.SafeAreaLayoutGuide.TopAnchor, leading: View.LeadingAnchor, trailing: View.TrailingAnchor);
            ic_qol.Anchor(top: header.TopAnchor, leading: header.LeadingAnchor);
            ic_edit.Anchor(top: header.TopAnchor, trailing: header.TrailingAnchor);
            qol_title.Anchor(top: View.SafeAreaLayoutGuide.TopAnchor, leading: ic_qol.RightAnchor, trailing: ic_edit.RightAnchor, padding: new UIEdgeInsets(0, 10, 0, 0));   <=== fails here.
        }
       public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
            // Release any cached data, images, etc that aren't in use.
        }
    }

    internal static class extensions
    {
        // https://stackoverflow.com/a/48601685/15186
        internal static void SizeToImage(this UIImageView uIImageView)
        {
            //Grab loc
            var xC = uIImageView.Center.X;
            var yC = uIImageView.Center.Y;

            //Size to fit
            uIImageView.Frame = new CGRect(x: 0, y: 0, width: uIImageView.Image.Size.Width / 2, height: uIImageView.Image.Size.Height / 2);

            //Move to loc
            uIImageView.Center = new CGPoint(x: xC, y: yC);
        }

        internal static void FillParentView(this UIView uIView)
        {
            uIView.Anchor(top: uIView.Superview?.TopAnchor, leading: uIView.Superview?.LeadingAnchor, bottom: uIView.Superview?.BottomAnchor, trailing: uIView.Superview.TrailingAnchor);
        }

        internal static void AnchorSize(this UIView uIView, UIView to)
        {
            uIView.WidthAnchor.ConstraintEqualTo(to.WidthAnchor).Active = true;
            uIView.HeightAnchor.ConstraintEqualTo(to.HeightAnchor).Active = true;
        }

        internal static void Anchor(this UIView uIView, NSLayoutYAxisAnchor top = null, NSLayoutXAxisAnchor leading = null, NSLayoutYAxisAnchor bottom = null, NSLayoutXAxisAnchor trailing = null, UIEdgeInsets padding = default, CGSize size= default)
        {
            uIView.TranslatesAutoresizingMaskIntoConstraints = false;
            if (top != null)
            {
                uIView.TopAnchor.ConstraintEqualTo(top, padding.Top).Active = true;
            }

            if (leading != null)
            {
                uIView.LeadingAnchor.ConstraintEqualTo(leading, padding.Left).Active = true; <=== fails here.
            }

            if (bottom != null)
            {
                uIView.BottomAnchor.ConstraintEqualTo(bottom, -padding.Bottom).Active = true;
            }

            if (trailing != null)
            {
                uIView.TrailingAnchor.ConstraintEqualTo(trailing, -padding.Right).Active = true;
            }

            if ( size.Width != 0)
            {
                uIView.WidthAnchor.ConstraintEqualTo(size.Width).Active = true;
            }

            if ( size.Height != 0)
            {
                uIView.HeightAnchor.ConstraintEqualTo(size.Height).Active = true;
            }
        }
    }

I'm trying to constraint the Leading anchor of the Label to the trailing anchor of the image but it fails with the following exception:

A constraint cannot be made between <NSLayoutXAxisAnchor:0x280bcb980 "UILabel:0x1015132a0'Quality of Life'.leading">' and '<NSLayoutXAxisAnchor:0x280bcb840 "UIImageView:0x101510ea0.right">' because their units are not compatible.

I understand that their units may be incompatible but how can I do that anyways? How to make the units compatible?

Credits: The extensions methods I'm using are originally created during this video tutorial . I ported them to c#.

You can achieve this layout easily with a "horizontal" stack view.

Note: I don't use xamarin / c#, so I'll just describe what you want to do.

  • create the stack view
  • set its properties to
    • Axis: Horizontal
    • Distribution: Fill
    • Alignment: Center (this is the vertical alignment of the subviews)
    • Spacing: 8 (you probably want a little space between subviews)
  • create 2 image views
    • load an image into each image view
    • set width and height constraints on the image views as desired (it looks like you are using 1/2 the height and width of your images?)
  • create a label
  • add the image views and label to the stack view as arranged subviews
    • header.AddArrangedSubview(ic_qol);
    • header.AddArrangedSubview(qol_title);
    • header.AddArrangedSubview(ic_edit);
  • add the stack view to the view
    • View.AddSubview(header);

The last step will be to add constraints to put the stack view at the top, spanning the width of the view:

// Get the parent view's layout
var margins = View.LayoutMarginsGuide;

// Pin the top edge of the stackView to the margin
header.TopAnchor.ConstraintEqualTo (margins.TopAnchor).Active = true;

// Pin the leading edge of the stackView to the margin
header.LeadingAnchor.ConstraintEqualTo (margins.LeadingAnchor).Active = true;

// Pin the trailing edge of the stackView to the margin
header.TrailingAnchor.ConstraintEqualTo (margins.TrailingAnchor).Active = true;

The result should look about like this:

在此处输入图片说明

Each imageView is constrained to 40-pts width and height-equalTo-width. The label has a cyan background to make it easy to see the layout, and text alignment is center. The outlines are just there so we can see the stackView's frame.

and at runtime, without the frame outlines:

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM