UIAlertController - 在Actionsheet中添加自定义视图

56

当我们尝试像截图中那样附加图像时,我希望将操作表呈现为iOS上消息应用程序中显示的样式。

我发现在新的UIAlertController中,我们无法适配任何自定义视图。有什么方法可以完全实现这个效果吗?

我的代码看起来非常标准。

    let alertController = UIAlertController(title: "My AlertController", message: "tryna show some images here man", preferredStyle: UIAlertControllerStyle.ActionSheet)

        let okAction = UIAlertAction(title: "oks", style: .Default) { (action: UIAlertAction) -> Void in
        alertController.dismissViewControllerAnimated(true, completion: nil)
    }
    let cancelAction = UIAlertAction(title: "Screw it!", style: .Cancel) { (action: UIAlertAction) -> Void in
        alertController.dismissViewControllerAnimated(true, completion: nil)
    }

    alertController.addAction(okAction)
    alertController.addAction(cancelAction)

    self.presentViewController(alertController, animated: true, completion: nil)

输入图像描述


我试图解决UIAlertController的限制问题,但无论我如何处理,都不够好。如果你仍然在苦苦挣扎,请看看我创建的 一个库,它可能会有所帮助。它允许您创建具有一堆内置类型的自定义表单。它还可以扩展和重新设计。 - Daniel Saidi
@CalZone你成功做到了吗? - Arshad Shaik
7个回答

92

UIAlertController扩展自UIViewController,具有视图属性。您可以向该视图添加子视图,以满足您的需求。唯一的问题是正确调整警报控制器的大小。您可以像这样做,但这很容易在下次苹果调整UIAlertController设计时出现问题。

Swift 3

let alertController = UIAlertController(title: "\n\n\n\n\n\n", message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
    
let margin:CGFloat = 10.0
let rect = CGRect(x: margin, y: margin, width: alertController.view.bounds.size.width - margin * 4.0, height: 120)
let customView = UIView(frame: rect)
    
customView.backgroundColor = .green
alertController.view.addSubview(customView)
    
let somethingAction = UIAlertAction(title: "Something", style: .default, handler: {(alert: UIAlertAction!) in print("something")})
    
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {(alert: UIAlertAction!) in print("cancel")})
    
alertController.addAction(somethingAction)
alertController.addAction(cancelAction)

DispatchQueue.main.async {
    self.present(alertController, animated: true, completion:{})
}

Swift

let alertController = UIAlertController(title: "\n\n\n\n\n\n", message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)

let margin:CGFloat = 10.0
let rect = CGRect(x: margin, y: margin, width: alertController.view.bounds.size.width - margin * 4.0, height: 120)
let customView = UIView(frame: rect)

customView.backgroundColor = .green
alertController.view.addSubview(customView)

let somethingAction = UIAlertAction(title: "Something", style: .default, handler: {(alert: UIAlertAction!) in print("something")})

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {(alert: UIAlertAction!) in print("cancel")})

alertController.addAction(somethingAction)
alertController.addAction(cancelAction)

self.present(alertController, animated: true, completion:{})

Objective-C

  UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"\n\n\n\n\n\n" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  
  CGFloat margin = 8.0F;
  UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(margin, margin, alertController.view.bounds.size.width - margin * 4.0F, 100.0F)];
  customView.backgroundColor = [UIColor greenColor];
  [alertController.view addSubview:customView];
  
  UIAlertAction *somethingAction = [UIAlertAction actionWithTitle:@"Something" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {}];
  UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {}];
  [alertController addAction:somethingAction];
  [alertController addAction:cancelAction];
  [self presentViewController:alertController animated:YES completion:^{}];

话虽如此,一种更少hacky的方法是创建自己的视图子类,其工作方式类似于UIAlertController的UIAlertActionStyle布局。 实际上,在iOS 8和iOS 9中,相同的代码看起来略有不同。

iOS 8 输入图像描述

iOS 9 输入图像描述

iOS 10 输入图像描述


太棒了,真的很有帮助。我仍然在边距后面得到一条线,我想我会在绿色视图周围放置一个容器视图来解决这个问题,边距为0。 - CalZone
谢谢,伙计。这太棒了。只是对自定义视图的宽度进行了轻微调整,但一切都很好!干杯。 - Felipe
2
这就是为什么我在我的回答中提到,“一个更少hacky的方法是创建自己的视图子类,其工作方式类似于UIAlertController的UIAlertActionStyle布局。实际上,在iOS 8和iOS 9中,相同的代码看起来略有不同。”问题是要向UIAlertController添加子视图,而被接受的答案确实做到了这一点。 - Keller
1
我认为UIAlertController的初始框架大小与UIView相同。在iPhone上,上述代码可以正常工作,因为alertController占据了设备的整个宽度。在iPad上,alertController会被调整大小。要使子视图自动调整大小,请设置调整掩码customView.autoresizingMask = UIViewAutoresizingFlexibleWidth; - Eric D'Souza
天才...无话可说!! 谢谢! - Mattia Ducci
显示剩余5条评论

44

目前我发现使用AutoLayout约束的最干净的解决方案:

func showPickerController() {
    let alertController = UIAlertController(title: "Translation Language", message: nil, preferredStyle: .actionSheet)
    let customView = UIView()
    alertController.view.addSubview(customView)
    customView.translatesAutoresizingMaskIntoConstraints = false
    customView.topAnchor.constraint(equalTo: alertController.view.topAnchor, constant: 45).isActive = true
    customView.rightAnchor.constraint(equalTo: alertController.view.rightAnchor, constant: -10).isActive = true
    customView.leftAnchor.constraint(equalTo: alertController.view.leftAnchor, constant: 10).isActive = true
    customView.heightAnchor.constraint(equalToConstant: 250).isActive = true

    alertController.view.translatesAutoresizingMaskIntoConstraints = false
    alertController.view.heightAnchor.constraint(equalToConstant: 430).isActive = true

    customView.backgroundColor = .green

    let selectAction = UIAlertAction(title: "Select", style: .default) { (action) in
        print("selection")
    }

    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    alertController.addAction(selectAction)
    alertController.addAction(cancelAction)
    self.present(alertController, animated: true, completion: nil)
}

输出:

enter image description here


我很高兴有人做得很好 :-) - GregJaskiewicz
关于这个主题,这是最好的答案。不知道为什么这里没有被接受。 - Ankur Lahiry
如果对话框在某些条件下具有标题,它将会出现问题。 根据标题长度和用户字体大小首选项,标题可能会与自定义视图重叠。应计算顶部填充。我没有太多的iOS经验,否则会发布解决方案。 - IliaEremin
1
根据苹果公司UIAlertController文档所述:“重要提示:此类的视图层次结构是私有的,不得进行修改。” - Jamie McDaniel

20

我为UIAlertController编写了一个扩展(使用Swift 4),它解决了自动布局的布局问题。如果由于UIAlertController布局的未来更改而出现问题,甚至还有回退消息字符串。

import Foundation

extension UIAlertController {
    
    /// Creates a `UIAlertController` with a custom `UIView` instead the message text.
    /// - Note: In case anything goes wrong during replacing the message string with the custom view, a fallback message will
    /// be used as normal message string.
    ///
    /// - Parameters:
    ///   - title: The title text of the alert controller
    ///   - customView: A `UIView` which will be displayed in place of the message string.
    ///   - fallbackMessage: An optional fallback message string, which will be displayed in case something went wrong with inserting the custom view.
    ///   - preferredStyle: The preferred style of the `UIAlertController`.
    convenience init(title: String?, customView: UIView, fallbackMessage: String?, preferredStyle: UIAlertController.Style) {
        
        let marker = "__CUSTOM_CONTENT_MARKER__"
        self.init(title: title, message: marker, preferredStyle: preferredStyle)
        
        // Try to find the message label in the alert controller's view hierarchie
        if let customContentPlaceholder = self.view.findLabel(withText: marker),
            let customContainer =  customContentPlaceholder.superview {
            
            // The message label was found. Add the custom view over it and fix the autolayout...
            customContainer.addSubview(customView)
            
            customView.translatesAutoresizingMaskIntoConstraints = false
            customContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[customView]-|", options: [], metrics: nil, views: ["customView": customView]))
            customContainer.addConstraint(NSLayoutConstraint(item: customContentPlaceholder,
                                                             attribute: .top,
                                                             relatedBy: .equal,
                                                             toItem: customView,
                                                             attribute: .top,
                                                             multiplier: 1,
                                                             constant: 0))
            customContainer.addConstraint(NSLayoutConstraint(item: customContentPlaceholder,
                                                             attribute: .height,
                                                             relatedBy: .equal,
                                                             toItem: customView,
                                                             attribute: .height,
                                                             multiplier: 1,
                                                             constant: 0))
            customContentPlaceholder.text = ""
        } else { // In case something fishy is going on, fall back to the standard behaviour and display a fallback message string
            self.message = fallbackMessage
        }
    }
}

private extension UIView {
    
    /// Searches a `UILabel` with the given text in the view's subviews hierarchy.
    ///
    /// - Parameter text: The label text to search
    /// - Returns: A `UILabel` in the view's subview hierarchy, containing the searched text or `nil` if no `UILabel` was found.
    func findLabel(withText text: String) -> UILabel? {
        if let label = self as? UILabel, label.text == text {
            return label
        }
        
        for subview in self.subviews {
            if let found = subview.findLabel(withText: text) {
                return found
            }
        }
        
        return nil
    }
}

以下是使用示例:

// Create a custom view for testing...
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = .red

// Set the custom view to a fixed height. In a real world application, you could use autolayouted content for height constraints
customView.addConstraint(NSLayoutConstraint(item: customView,
                                            attribute: .height,
                                            relatedBy: .equal,
                                            toItem: nil,
                                            attribute: .notAnAttribute,
                                            multiplier: 1,
                                            constant: 100))

// Create the alert and show it
let alert = UIAlertController(title: "Alert Title",
                                customView: customView,
                                fallbackMessage: "This should be a red rectangle",
                                preferredStyle: .actionSheet)

alert.addAction(UIAlertAction(title: "Yay!", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

self.present(alert, animated: true, completion: nil)

这将显示类似于以下内容:

在此输入图片描述

2
没有使用私有API,所以我不知道为什么苹果会拒绝它。 - Zaggo
3
面临的唯一问题是 customView 显示为灰色而不是红色。有任何想法吗? - Bhavin_m
这看起来是一个不错、干净的解决方案,但我认为人们强烈建议不要扩展 UIAlertController,因为苹果不希望你这样做,并且可能会在未来的更新中破坏你的“hack”。这个解决方案也可能仍然存在这个问题吗? - Neph
我尽可能优雅地处理任何失败。如果苹果进行了任何结构更改,最坏的情况就是警报只显示回退文本消息,而不是自定义视图,这也正是API中为此情况准备的。 - Zaggo
看起来最终到了放弃那些自定义 UIAlertController 的时候了。更多信息请查看这里 - nayem
显示剩余3条评论

4

对于懒人,这是@Keller的回答的Swift 3.0和iOS >= 9优化版本:

let alertController = UIAlertController(title: "\n\n\n\n\n\n", message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)

let margin:CGFloat = 10.0
let rect = CGRect(x: margin, y: margin, width: alertController.view.bounds.size.width - margin * 4.0, height: 120)
let customView = UIView(frame: rect)

customView.backgroundColor = .green
alertController.view.addSubview(customView)

let somethingAction = UIAlertAction(title: "Something", style: .default, handler: {(alert: UIAlertAction!) in print("something")})

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {(alert: UIAlertAction!) in print("cancel")})

alertController.addAction(somethingAction)
alertController.addAction(cancelAction)

self.present(alertController, animated: true, completion:{})

3

我试图绕过UIAlertController的限制,但无论如何尝试,都不够好。如果你还在苦苦挣扎,我创建了一个库可能会有所帮助。它允许您创建具有许多内置类型的自定义表单。还可以进行扩展和重新设计。


0

我最喜欢@Zaggo的答案,但我决定留下我的实现:

func showActionSheet(_ sender: UIViewController) { 

    let actionSheet = UIAlertController(title: "Debug menu", message: nil, preferredStyle: .actionSheet)
    
    let debugActions = [
        UIAlertAction(title: "Send mail", style: .default) { _ in
     
        },
        UIAlertAction(title: "Drop database", style: .default) { _ in

        },
        UIAlertAction(title: "Show logs console", style: .default) { _ in

        }
    ]
    
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in
        actionSheet.dismiss(animated: true)
    }
    
    debugActions.forEach(actionSheet.addAction)
    actionSheet.addAction(cancelAction)
    
    guard let actionSheetStackView = actionSheet.view.findStack() else { return }
    
    let customView = UIView()
    customView.translatesAutoresizingMaskIntoConstraints = false
    customView.backgroundColor = .red
    actionSheet.view.addSubview(customView)
    
    customView.translatesAutoresizingMaskIntoConstraints = false
    customView.topAnchor.constraint(equalTo: actionSheet.view.topAnchor, constant: 45).isActive = true
    customView.bottomAnchor.constraint(equalTo: actionSheetStackView.topAnchor).isActive = true
    customView.rightAnchor.constraint(equalTo: actionSheet.view.rightAnchor).isActive = true
    customView.leftAnchor.constraint(equalTo: actionSheet.view.leftAnchor).isActive = true
    customView.heightAnchor.constraint(equalToConstant: 50).isActive = true
    
    sender.present(actionSheet, animated: true)
}

func findStack() -> UIStackView? {
    if let stack = self as? UIStackView {
        return stack
    }
    
    for subview in self.subviews {
        if let found = subview.findStack() {
            return found
        }
    }
    
    return nil
}

result


-1
这是@Cesare解决方案的Objective-C版本。
- (void) showPickerController {
    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"Translation Language" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
    UIView *customView = [[UIView alloc] init];
    [alertController.view addSubview:customView];
    customView.translatesAutoresizingMaskIntoConstraints = NO;
    [customView.topAnchor constraintEqualToAnchor:alertController.view.topAnchor constant:45].active = YES;
    [customView.rightAnchor constraintEqualToAnchor:alertController.view.rightAnchor constant:-10].active = YES;
    [customView.leftAnchor constraintEqualToAnchor:alertController.view.leftAnchor constant:10].active = YES;
    [customView.heightAnchor constraintEqualToConstant:250].active = YES;

    alertController.view.translatesAutoresizingMaskIntoConstraints = NO;
    [alertController.view.heightAnchor constraintEqualToConstant:430].active = YES;
    
    customView.backgroundColor = [UIColor greenColor];
    
    UIAlertAction* selectAction = [UIAlertAction actionWithTitle:@"Select" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
    }];
    UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
    }];
    [alertController addAction:selectAction];
    [alertController addAction:cancelAction];
    
    [self presentViewController:alertController animated:YES completion:nil];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接