首页 > 解决方案 > Adding UIImageView to UIAlertController

问题描述

Goal: add an image above an UIAlertController's Title label by subclassing UIAlertController and adding new line characters, \n, to the title string to make space for the UIImageView

Desire goal image Current current image

As one can see, able to add the image to the UIAlertController successfully but the image is not being spacing/placed above the Title. It appears to be adding to the center of the alert. How to space the image correctly above the UIAlertController title?

Current code:

namespace XamarinFormsApp1.Extensions
{
    public class AlertController : UIAlertController
    {
        private string originalTitle;
        private string spaceAdjustedTitle;
        private UIImageView imageView = null;
        private CoreGraphics.CGSize previousImgViewSize 
            = CoreGraphics.CGSize.Empty;
        public override UIAlertControllerStyle PreferredStyle
        {
            get
            {
                return UIAlertControllerStyle.Alert;
            }
        }

    public override string Title
    {
        get
        {
            return originalTitle;
        }
        set
        {
            if (Title != spaceAdjustedTitle ||
                string.IsNullOrEmpty(Title) || 
                string.IsNullOrEmpty(spaceAdjustedTitle))
            {
                originalTitle = value;
            }
        }
    }

    public void setTitleImage(UIImage image)
    {
        if (this.imageView == null)
        {
            UIImageView imageView = new UIImageView(image);
            this.View.AddSubview(imageView);
            this.imageView = imageView;
            return;
        }
        imageView.Image = image;
    }

    public override void ViewDidLayoutSubviews()
    {
        if (imageView == null)
        {
            base.ViewDidLayoutSubviews();
            return;
        }
        //  Adjust title if image size has changed
        if (previousImgViewSize != imageView.Bounds.Size)
        {
            previousImgViewSize = imageView.Bounds.Size;
            adjustTitle(imageView);
        }
        //  Position `imageView`
        var linesCount = newLinesCount(imageView);
        var padding = Constants.Padding(PreferredStyle);
        var x = View.Bounds.Width / 2.0;
        var y = padding + linesCount * lineHeight / 2.0;
        CoreGraphics.CGPoint cgPoint = imageView.Center;
        cgPoint.X = (nfloat)x;
        cgPoint.Y = (nfloat)y;
        imageView.Center = cgPoint;
        base.ViewDidLayoutSubviews();
    }

    private void adjustTitle(UIImageView imageView)
    {
        var linesCount = (int)newLinesCount(imageView);
        var lines = Enumerable
            .Range(1, linesCount)
            .Select(i => "\n")
            .Aggregate((c, n) => $"{c}{n}");
        spaceAdjustedTitle = lines + (originalTitle ?? "");
        Title = spaceAdjustedTitle;
    }

    private double newLinesCount(UIImageView imageView)
    {
        return Math.Ceiling(
            imageView.Bounds.Height / lineHeight);
    }

    private float lineHeight
    {
        get
        {
            UIFontTextStyle style = this.PreferredStyle
                == UIAlertControllerStyle.Alert 
                ? UIFontTextStyle.Headline 
                : UIFontTextStyle.Callout;
            return (float)UIFont
                .GetPreferredFontForTextStyle(style)
                .PointSize;
        }
    }

    struct Constants
    {
        static float paddingAlert = 22;
        static float paddingSheet = 11;
        public static float Padding(UIAlertControllerStyle style)
        {
            return style == UIAlertControllerStyle.Alert 
                ? Constants.paddingAlert 
                : Constants.paddingSheet;
        }
    }
}
}

Note: Credit to @stringCode for image and swift solution, see.

标签: c#xamarinuiimageviewuialertcontroller

解决方案


UIAlertViewController is not meant to be subclassed.

An extract from the documentation says:

enter image description here

You could still get the UI you desire by using a UIViewController with transparency on the View and a subview with the layout you desire.

You would need to also set these two properties: ModalTransitionStyle and ModalPresentationStyle to UIModalTransitionStyle.CrossDissolve and UIModalPresentationStyle.OverCurrentContext respectively if you want your custom UIAlertController to behaves the same as a UIAlertController

Update:

This is what I meant you could do:

In the Main.Storyboard drop a UIViewController and update the design as you wish. Following the image you posted above I created the UI as seen below:

enter image description here

That's an Image, 2 UILabels for the Title and Message and 3 buttons for the 3 different actions (Default, Destroy, Cancel). All these controls are inside a UIView with White background. For the example I called it ContentView

Adding the 3 button on the UI seems to be the easiest way to work with this and then hide/show them when you are about to present your alert. You could also create the buttons on the fly based on the actions you wanna show. This is up to you.

Create a ViewController Class, I called it NiceAlertController, and assign it to the ViewController in the Storyboard. Also, make sure to create back properties (Outlets) for all the UIControls (Label, Button, Image, etc) so you can access it from the ViewController class.

Here more information about how to work with iOS Storyboard on the designer

Now in your class you will need to add the code to make it work.

In your class to make the view transparent you will need to add this to your ViewDidLoad method:

public override void ViewDidLoad()
{
    base.ViewDidLoad();
    this.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.2f);
    this.View.Opaque = false;
}

Also, we could mimic the way UIAlertControllers are created and create our method like that one:

public static NiceAlertController Create(string title, string message, UIImage image = null)
{
    //New instance of your ViewController UI
    var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
    var alertController = storyboard.InstantiateViewController(nameof(NiceAlertController)) as NiceAlertController;

    //Using the same transition and presentation style as the UIAlertViewController
    alertController.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
    alertController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;

    //This assumes your properties are called Title, Message and Image 
    alertController.Title = title;
    alertController.Message = message;
    alertController.Image = image;

    return alertController;
}

The 3 properties used above (Title, Message and Image) looks like this:

public new string Title { get; private set; }

public string Message { get; private set; }

public UIImage Image { get; private set; }

Why these properties? because by the time you create the class the Controls on the view are not yet available. They will be available only after the View is loaded. This is why we will need to add other changes like the one below.

Here we are setting the values to the Controls on the UI

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);

    this.titleLabel.Text = Title;
    this.messageLabel.Text = Message;

    //If you don't set an image while Create, it will use the default image you set on the Designer.
    if (Image != null)
    {
        this.imageView.Image = Image;
    }
}    

Now from any other ViewController you can call this ViewController as you would call an AlertViewController:

private void ShowMyAlertController()
{
    var alert = NiceAlertController.Create("Hello", "My nice alert controller");
    this.PresentViewController(alert, true, null);
}

And it should look like this:

enter image description here

To handle the Actions (What to do when the buttons are tapped) you could create specific methods like:

public void AddDefaultAction(string title, Action action)
{
    //Logic here
}

public void AddCancelAction(string title, Action action)
{
    //Logic here
}

public void AddDestructiveAction(string title, Action action)
{
    //Logic here
}

Hope this gives you the idea of how to create custom UIViewcontroller and make it look like a UIAlertViewController.

Hope this helps.-


推荐阅读