你如何呈现一系列UIAlertControllers?

时间:2018-06-13 16:25:11

标签: ios swift uialertcontroller

我有一个可以同时从服务器下载许多出版物的应用程序。对于应用程序中已存在的每个发布,我想提示用户是否要覆盖现有版本。

是否有任何干净的方式来呈现UIAlertControllers,以便当用户回答一个时,该应用程序会呈现下一个?

4 个答案:

答案 0 :(得分:2)

这是输出

enter image description here

虽然在后续声明中调用了两个警报操作,但只有在用户与屏幕上的警报交互后才会显示第二个警报我的意思是仅在点击确定或取消后才会显示。

如果这是您想要的,正如我在评论中提到的那样,您可以使用异步操作和操作队列,最大并发操作为1

这是代码。

首先声明自己的异步操作

struct AlertObject {
    var title : String! = nil
    var message : String! = nil
    var successAction : ((Any?) -> ())! = nil
    var cancelAction : ((Any?) -> ())! = nil

    init(with title : String, message : String, successAction : @escaping ((Any?) -> ()), cancelAction : @escaping ((Any?) -> ())) {
        self.title = title
        self.message = message
        self.successAction = successAction
        self.cancelAction = cancelAction
    }
}


class MyAsyncOperation : Operation {
    var alertToShow : AlertObject! = nil
    var finishedStatus : Bool = false

    override init() {
        super.init()
    }

    override var isFinished: Bool {
        get {
            return self.finishedStatus
        }
        set {
            self.willChangeValue(forKey: "isFinished")
            self.finishedStatus = newValue
            self.didChangeValue(forKey: "isFinished")
        }
    }

    override var isAsynchronous: Bool{
        get{
            return true
        }
        set{
            self.willChangeValue(forKey: "isAsynchronous")
            self.isAsynchronous = true
            self.didChangeValue(forKey: "isAsynchronous")
        }
    }

    required convenience init(with alertObject : AlertObject) {
        self.init()
        self.alertToShow = alertObject
    }

    override func start() {
        if self.isCancelled {
            self.isFinished = true
            return
        }
        DispatchQueue.main.async {
            let alertController = UIAlertController(title: self.alertToShow.title, message: self.alertToShow.message, preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
                self.alertToShow.successAction(nil) //pass data if you have any
                self.operationCompleted()
            }))
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
                self.alertToShow.cancelAction(nil) //pass data if you have any
                self.operationCompleted()
            }))
            UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
        }
    }

    func operationCompleted() {
        self.isFinished = true
    }
}

虽然代码看起来很复杂但实质上非常简单。您正在做的就是覆盖操作的isFinishedisAsynchronous属性。

如果您知道Operation队列如何与Operation一起使用,那么我应该非常清楚为什么要覆盖这些属性。如果万一你不知道! OperationQueue在Operation的isFinished属性上使用KVO来开始执行Operation queue中的下一个相关操作。

当OperationQueue的最大并发操作数为1时,操作的isFinished标志决定下一次操作何时执行:)

因为用户可能会在警报的某个不同时间帧执行操作,所以使操作异步(默认操作是同步的)并覆盖isFinised属性很重要。

AlertObject是一个方便的对象,用于保存警报的元数据。您可以根据需要对其进行修改:)

多数民众赞成。现在无论viewController想要显示什么警报,它都可以简单地使用MyAsyncOperation来确保你只有一个Queue实例:)

这是我使用它的方式

    let operationQueue = OperationQueue() //make sure all VCs use the same operation Queue instance :)
    operationQueue.maxConcurrentOperationCount = 1

    let alertObject = AlertObject(with: "First Alert", message: "Success", successAction: { (anything) in
        debugPrint("Success action tapped")
    }) { (anything) in
        debugPrint("Cancel action tapped")
    }

    let secondAlertObject = AlertObject(with: "Second Alert", message: "Success", successAction: { (anything) in
        debugPrint("Success action tapped")
    }) { (anything) in
        debugPrint("Cancel action tapped")
    }

    let alertOperation = MyAsyncOperation(with: alertObject)
    let secondAlertOperation = MyAsyncOperation(with: secondAlertObject)
    operationQueue.addOperation(alertOperation)
    operationQueue.addOperation(secondAlertOperation)

如您所见,我在后续声明中添加了两个警报操作。即使在用户解除当前显示的警报后,该警报也会显示:)

希望这有帮助

答案 1 :(得分:1)

尽管使用Queue的答案非常好,但你可以轻松实现同样的:

var messages: [String] = ["first", "second"]

func showAllerts() {
    guard let message = messages.first else { return }
    messages = messages.filter({$0 != message})
    let alert = UIAlertController(title: "title", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] (action) in
        // do something
        self?.showAllerts()
    }))
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (action) in
        self?.showAllerts()
    }))
    present(alert, animated: true, completion: nil)
}

(用你想要的任何东西替换消息数组)

答案 2 :(得分:0)

我建议创建一个队列数据结构(https://github.com/raywenderlich/swift-algorithm-club/tree/master/Queue)。

警报对象按照初始化警报的顺序排队。当用户选择其中一个警报的操作时,请将下一个可能的警报出列并显示。

答案 3 :(得分:0)

我的应用程序中遇到了同样的问题,并尝试了几种解决方案,但是所有解决方案都很混乱。然后我想到了一种非常简单有效的方法:使用延迟重试演示文稿,直到可以显示为止。这种方法更加简洁,因为您不需要在多个地方使用协调的代码,也不必破解操作处理程序。

根据您的用例,您可能会担心这种方法不会不必要地保留警报的顺序,在这种情况下,您可以轻松地对其进行调整,以将警报存储在数组中以保留顺序,仅显示和删除警报。每次都首先在数组中。

此代码将覆盖UIViewController中的标准表示方法,在用于显示警报的子类中使用它。如果需要的话,也可以将其改编为应用程序级别的方法,该方法从rootViewController派生而来,以找到显示最多的VC并从那里显示内容,等等。

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
  // cannot present if already presenting.
  if (self.presentedViewController) {
    // cannot present now, try again in 100ms.
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      // make sure we ourselve are still presented and able to present.
      if (self.presentingViewController && !self.isBeingDismissed) {
        // retry on self
        [self presentViewController:viewControllerToPresent animated:flag completion:completion];
      }
    });
  } else {
    // call super to really do it
    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
  }
}