You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift自定义UIAlertController方案:如何实现含进度条、列表的弹窗?

Great question! Apple’s documentation makes it crystal clear that subclassing UIAlertController is not recommended—its view hierarchy is private, so tinkering with it will almost certainly lead to breakages in future iOS updates. Instead, here are two solid, future-proof approaches tailored to your two use cases:

1. ProgressBar Popup

Apple added the contentViewController property to UIAlertController in iOS 16, which lets you inject a custom view controller directly into the alert. This is the cleanest, most supported way:

// Custom view controller holding the progress bar
class ProgressAlertContentVC: UIViewController {
    private let progressView = UIProgressView(progressViewStyle: .default)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupProgressViewLayout()
    }
    
    private func setupProgressViewLayout() {
        view.addSubview(progressView)
        progressView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            progressView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8),
            progressView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8)
        ])
    }
    
    func updateProgress(_ progress: Float) {
        progressView.setProgress(progress, animated: true)
    }
}

// How to use it
let alert = UIAlertController(title: "Loading", message: "Please wait...", preferredStyle: .alert)
let progressContentVC = ProgressAlertContentVC()
alert.contentViewController = progressContentVC

// Add a cancel button if needed
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
    // Handle cancellation logic here
}
alert.addAction(cancelAction)

present(alert, animated: true)

// Later, update the progress as needed
progressContentVC.updateProgress(0.6)

Option 2: Compatible with iOS 15 and Earlier

If you need to support older iOS versions, build a fully custom UIViewController and present it as a popup. This gives you full control over the UI without relying on private APIs:

class CustomProgressAlertVC: UIViewController {
    private let progressView = UIProgressView(progressViewStyle: .default)
    private let titleLabel = UILabel()
    private let messageLabel = UILabel()
    
    init(title: String, message: String) {
        super.init(nibName: nil, bundle: nil)
        self.titleLabel.text = title
        self.messageLabel.text = message
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupFullUI()
    }
    
    private func setupFullUI() {
        view.backgroundColor = .systemBackground
        view.layer.cornerRadius = 12
        view.clipsToBounds = true
        
        // Build vertical stack for title, message, progress bar
        let stackView = UIStackView(arrangedSubviews: [titleLabel, messageLabel, progressView])
        stackView.axis = .vertical
        stackView.spacing = 12
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
            stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
            progressView.heightAnchor.constraint(equalToConstant: 4)
        ])
        
        // Style text
        titleLabel.font = .systemFont(ofSize: 17, weight: .semibold)
        messageLabel.font = .systemFont(ofSize: 14)
        messageLabel.textColor = .secondaryLabel
        messageLabel.numberOfLines = 0
    }
    
    func updateProgress(_ progress: Float) {
        progressView.setProgress(progress, animated: true)
    }
}

// How to use it
let progressAlert = CustomProgressAlertVC(title: "Loading", message: "Please wait...")
progressAlert.modalPresentationStyle = .overFullScreen
progressAlert.modalTransitionStyle = .crossDissolve

// Add a semi-transparent backdrop
let backdropView = UIView(frame: progressAlert.view.bounds)
backdropView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
progressAlert.view.insertSubview(backdropView, at: 0)

present(progressAlert, animated: true)

// Update progress later
progressAlert.updateProgress(0.8)

2. Error Message List Popup

Option 1: iOS 16+ (Official Approach)

Again, leverage the contentViewController property to inject a table view or stack view displaying your errors:

class ErrorListContentVC: UIViewController {
    private let errorMessages: [String]
    private let tableView = UITableView()
    
    init(errorMessages: [String]) {
        self.errorMessages = errorMessages
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }
    
    private func setupTableView() {
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            tableView.edgesAnchor.constraint(equalTo: view.edgesAnchor)
        ])
        
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ErrorCell")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 44
        tableView.separatorStyle = .none
    }
}

extension ErrorListContentVC: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        errorMessages.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ErrorCell", for: indexPath)
        cell.textLabel?.text = "• \(errorMessages[indexPath.row])"
        cell.textLabel?.font = .systemFont(ofSize: 14)
        cell.textLabel?.textColor = .systemRed
        cell.selectionStyle = .none
        return cell
    }
}

// How to use it
let errors = [
    "Invalid email format",
    "Password must be at least 8 characters",
    "Username already taken"
]

let alert = UIAlertController(title: "Validation Errors", message: "Please fix these issues:", preferredStyle: .alert)
let errorContentVC = ErrorListContentVC(errorMessages: errors)
alert.contentViewController = errorContentVC

let okAction = UIAlertAction(title: "OK", style: .default)
alert.addAction(okAction)

present(alert, animated: true)

Option 2: Compatible with Older iOS Versions

For broader compatibility, use a UIStackView to display error messages without a table view:

class ErrorListAlertVC: UIViewController {
    init(title: String, errorMessages: [String]) {
        super.init(nibName: nil, bundle: nil)
        setupErrorUI(title: title, errors: errorMessages)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupErrorUI(title: String, errors: [String]) {
        view.backgroundColor = .systemBackground
        view.layer.cornerRadius = 12
        view.clipsToBounds = true
        
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 17, weight: .semibold)
        
        // Stack for error messages
        let errorStack = UIStackView()
        errorStack.axis = .vertical
        errorStack.spacing = 8
        
        for error in errors {
            let errorLabel = UILabel()
            errorLabel.text = "• \(error)"
            errorLabel.font = .systemFont(ofSize: 14)
            errorLabel.textColor = .systemRed
            errorLabel.numberOfLines = 0
            errorStack.addArrangedSubview(errorLabel)
        }
        
        // OK button
        let okButton = UIButton(type: .system)
        okButton.setTitle("OK", for: .normal)
        okButton.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
        okButton.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
        
        // Main stack to hold all elements
        let mainStack = UIStackView(arrangedSubviews: [titleLabel, errorStack, okButton])
        mainStack.axis = .vertical
        mainStack.spacing = 16
        mainStack.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(mainStack)
        
        NSLayoutConstraint.activate([
            mainStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            mainStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            mainStack.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
            mainStack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
            okButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    @objc private func dismissAlert() {
        dismiss(animated: true)
    }
}

// How to use it
let errors = [
    "Invalid email format",
    "Password must be at least 8 characters",
    "Username already taken"
]

let errorAlert = ErrorListAlertVC(title: "Validation Errors", errorMessages: errors)
errorAlert.modalPresentationStyle = .overFullScreen
errorAlert.modalTransitionStyle = .crossDissolve

// Add backdrop
let backdropView = UIView(frame: errorAlert.view.bounds)
backdropView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
errorAlert.view.insertSubview(backdropView, at: 0)

present(errorAlert, animated: true)

Key Takeaway

The core principle here is to avoid subclassing UIAlertController entirely. Either use the official iOS 16+ API to extend its content, or build a custom popup controller from scratch. Both approaches keep your code stable across iOS updates while giving you the flexibility to add custom elements like progress bars or error lists.

内容的提问来源于stack exchange,提问作者Simon Hessner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:37:33