I have this simple class and an hypothetical protocol called 'ShowAlert', it's extension to do the default implementation and a default ViewController and it's ShowAlert protocol implementation.
protocol ShowAlert {
var titleForAlert: String! { get }
func messageForAlert() -> String!
func show()
}
extension ShowAlert where Self: UIViewController {
func show(){
let alert = UIAlertController(title: self.titleForAlert, message: self.messageForAlert(), preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showItNow(sender: AnyObject) {
self.show()
}
}
extension ViewController: ShowAlert {
var titleForAlert: String! {
get{
return "Foo"
}
}
func messageForAlert() -> String! {
return "Bar"
}
func show() {
// here I want to call the default implementation of the protocol to show the alert, then do something else
print("Good day sir!")
}
}
It's like on a subclass where I could call a 'super.show()' and then continue implementing whatever I want to do after that.
There's any way to do it? Or my logic go against what protocols are design for and that don't suppose to happen?
There is a simple solution: Just add a defaultShow method to the extension.
extension ShowAlert where Self: UIViewController {
func defaultShow(){
let alert = UIAlertController(title: self.titleForAlert, message: self.messageForAlert(), preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
func show() {
defaultShow()
}
}
So in your code you can just call defaultShow:
extension ViewController: ShowAlert {
// ...
func show() {
self.defaultShow()
print("Good day sir!")
}
}
There is also another solution where you can call .show() instead of .defaultShow(). However it uses casting and breaks encapsulation. If you want to see it let me know.
Related
in the simple language: can we create that alert Box as a reusable method
i want to made 1 Alert box in to the function.
like this.
// this code has separate file
import UIKit
struct AlertView {
public func showAlertBox(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
}))
return alert
}
}
and here is my caller ViewController file code.
#IBAction func submitPressed(_ sender: Any) {
let alertView = AlertView()
let alert = alertView.showAlertBox(title: "Hours Added", message: "Hours have been updated")
alert.present(alert, animated: true) {
self?.dismiss(animated: true, completion: nil)
self?.timeSubmitted = true
self?.performSegue(withIdentifier: "unwindToMyHours", sender: nil)
}
}
You need alert action to performing ok action.
You can modify your code by this
Here are the helper functions.
struct AlertView {
public static func showAlertBox(title: String, message: String, handler: ((UIAlertAction)->Void)?) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: handler))
return alert
}
}
extension UIAlertController {
func present(on viewController: UIViewController, completion: (() -> Void)? = nil) {
viewController.present(self, animated: true, completion: completion)
}
}
Usage
class ViewController: UIViewController {
#IBAction func submitPressed(_ sender: Any) {
AlertView.showAlertBox(title: "Hours Added", message: "Hours have been updated") { [weak self] action in
// Okay action code
}.present(on: self) { [weak self] in
self?.dismiss(animated: true, completion: nil)
self?.timeSubmitted = true
self?.performSegue(withIdentifier: "unwindToMyHours", sender: nil)
}
}
}
Note: self is dismissing so might be your alert is not presenting. You can present your alert on top most view controller. see this
Yes, you can create a shared alert controller. I would suggest making it a static method of your struct, or even a global function. It's silly to create an instance of your struct only to invoke a method that doesn't need any instance variables:
public static func alertBox(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
}))
return alert
}
}
And then you'd invoke it by saying
let alert = AlertView.alertBox(title: "title",message: "message" )
(Your function doesn't show the alert, it just creates it. I would therefore suggest naming it alertBox, not 'showAlertBox`.
Yes, you can use a shared alert controller. What I would suggest is making the AlertView struct, a singleton. You can change the struct as follows
struct AlertView {
// Shared instance
static let shared: AlertView = AlertView()
// Private initializer to prevent creating of new instances
private init() {}
public func showAlertBox(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
}))
return alert
}
}
By doing so, you will be able to create just one instance of AlertView and you have to use that single instance in your program. That way you won't have to create new instances of AlertView every time you need to display an alert. You can invoke it using,
let alert = AlertView.shared.showAlertBox(title: "Hours Added", message: "Hours have been updated")
Edit - You can refer this medium article to understand the singleton design patter
The best way is to perform simple encapsulation through extension, and complex encapsulation just loses applicability
example:
let alertVC = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
.addActionTitles(titles) { (alertVC, action) in
let actionIdx = alertVC.actions.firstIndex(of: action)
DDLog(actionIdx)
}
self.present(alertVC, animated: true, completion:{})
code:
public let kTitleSure = "Yes"
public let kTitleCancell = "No"
/// contentViewController
public let kAlertContentViewController = "contentViewController"
#objc public extension UIAlertController{
///add UIAlertAction
#discardableResult
func addActionTitles(_ titles: [String]? = [kTitleCancell, kTitleSure], handler: ((UIAlertController, UIAlertAction) -> Void)? = nil) -> Self {
titles?.forEach({ (string) in
let style: UIAlertAction.Style = string == kTitleCancell ? .cancel : .default
self.addAction(UIAlertAction(title: string, style: style, handler: { (action) in
handler?(self, action)
}))
})
return self
}
///add textField
#discardableResult
func addTextFieldPlaceholders(_ placeholders: [String]?, handler: ((UITextField) -> Void)? = nil) -> Self {
if self.preferredStyle != .alert {
return self
}
placeholders?.forEach({ (string) in
self.addTextField { (textField: UITextField) in
textField.placeholder = string
handler?(textField)
}
})
return self
}
#discardableResult
func setContent(vc: UIViewController, height: CGFloat) -> Self {
setValue(vc, forKey: kAlertContentViewController)
vc.preferredContentSize.height = height
preferredContentSize.height = height
return self
}
}
github
I declared a global variable for UIAlertViewController for me to be able to show and dismiss it in different method inside my class.
I displayed two kinds of alert: First, alert with button which will be displayed when an error is encountered or to display an information message. Second is an alert without button which will be displayed like a progress message.
Here is the sample code:
private var alert: UIAlertController? // global declaration
private func showProgressMessage(sender viewController: UIViewController, message alertMessage: String)
{
DispatchQueue.main.async
{
self.alert= UIAlertController(title: "", message: alertMessage, preferredStyle: .alert)
viewController.present(self.alert!, animated: true, completion: nil)
}
}
private func showAlertMessage(sender viewController: UIViewController, title alertTitle: String, message alertMessage: String)
{
DispatchQueue.main.async
{
self.alert= UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
self.alert!.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
viewController.present(self.alert!, animated: true, completion: nil)
}
}
private func method1()
{
DispatchQueue.global().async
{
// some code here
self.showProgressMessage(sender: self, message: "Processing...")
// some code here
}
}
private func method2()
{
// some code here
self.alert!.dismiss(animated: false)
{
self.showAlertMessage(sender: self, message: "Done")
}
self.displayOtherViewController()
}
private func displayOtherViewController()
{
self.alert?.dismiss(animated: false)
{
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "Sample")
{
let view = viewController as! SampleViewController
view .modalTransitionStyle = .crossDissolve
self.present(view , animated: true, completion: nil)
}
}
}
In method2, displaying the alert again will take a few seconds to display, same with the view controller.
What is the proper way to show and dismis the UIAlertController in Swift 4?
Seems like your code is initiated from a background thread.
Even dismiss must be called on main thread
Try this:
private func method2() {
DispatchQueue.main.async {
self.alert!.dismiss(animated: false) {
self.showAlertMessage(sender: self, message: "Done")
}
self.displayOtherViewController()
}
}
I want to write one function alert() and to run it. But I want to show this alert in any controllers without repeating the code.
For example: I have Presence.swift class and here I have some condition, as:
if myVar == 1 { // Just for presenting
runMyAlert()
}
and when in the background myVar == 1 user, regardless of the fact that where he is, on which screen, he gets alert on screen.
How can I do it without repeating my code? I tried to make it in AppDelegate as:
func alert(title : String,message : String,buttonTitle : String,window: UIWindow){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: buttonTitle, style: UIAlertActionStyle.Default, handler: nil))
window.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
And call it as:
if myVar == 1 {
AppDelegate().alert()
}
Developing on Swift you should know about protocols which can solve your problem easily. You can create a new protocol MyAlert and make default implementation for UIViewController class. And then just inherit your MyAlert protocol in view controller, where you need it, and call the function!
protocol MyAlert {
func runMyAlert()
}
extension MyAlert where Self: UIViewController {
func runMyAlert() {
let alert = UIAlertController(title: title, message: "message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "buttonTitle", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
So you can implement and call it like that:
class MyViewController: UIViewController, MyAlert {
override func viewDidLoad() {
runMyAlert() // for test
}
}
UPD:
code for your case:
protocol MyAlert {
func runMyAlert()
}
extension MyAlert where Self: UIViewController {
func runMyAlert() {
let alert = UIAlertController(title: title, message: "message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "buttonTitle", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
class OpenChatsTableViewController: UITableViewController, MyAlert, OneRosterDelegate, BuddyRequestProtocol {
override func viewDidLoad() {
runMyAlert()
}
}
Create a category on UIViewController, write a method there, calling which app will show an alert box.
Now create a new BaseViewController which will be inherited from UIViewController. Import your category in this BaseViewController;
From this point, whenever you create a new viewController, select your base class type as BaseViewController not UIViewController.
By doing so, you can call that alert showing method from anywhere.
I have a class to check internet connection that i found here: Check for internet connection with Swift
In my methods i use it:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if Reachability.isConnectedToNetwork() == false {
let alert = UIAlertView(title: "No Internet Connection", message: "Make sure your device is connected to the internet.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
return
}
}
but can i make a decorator or something to write something like:
#check_internet_connection
override func viewWillAppear(animated: Bool) {
}
or for example use it for all methods in class:
#check_internet_connection
class MyClass: UIViewController {
...
}
In Swift these are called Attributes. Currently (as of Swift 2.1), you cannot define your own.
Why not write a global function to handle this?
// In global scope
func check_internet_connection() -> Bool {
if Reachability.isConnectedToNetwork() == false {
let alert = UIAlertView(title: "No Internet Connection", message: "Make sure your device is connected to the internet.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
return false
} else {
return true
}
}
…
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if !check_internet_connection() { return }
}
Inheritance Approach
You can create ReachabilityAwareViewController as base class.
class ReachabilityAwareViewController: UIViewController{
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if Reachability.isConnectedToNetwork() == false {
// your code goes here
}
}
}
and create subclass that inherit all behaviour from ReachabilityAwareViewController.
class myViewController: ReachabilityAwareViewController{
// ..
}
Composition Approach
This approach looks more like the decorator. Using protocol extension
protocol ReachabilityAware{
func checkReachibility()
}
extension ReachabilityAware where Self: UIViewController{
func checkReachibility(){
if Reachability.isConnectedToNetwork() == false {
let alertController = UIAlertController(title: "No Internet Connection", message: "Make sure your device is connected to the internet.", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Cancel){ action in }
alertController.addAction(okAction)
presentViewController(alertController, animated: true, completion: nil)
}
}
}
class myViewController: UIViewController, ReachabilityAware{
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
checkReachibility()
}
}
I am creating a view controller in swift with a few text fields and an accept button which confirms the user's input. The accept button also checks if any of the text fields is empty. If so, it will pop up an alert saying something like it cannot be empty. if it is not empty, it will store the input and then jump to another view.
I created an separated function called checEmpty() which looks like this:
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
let alertController = UIAlertController(title: "Invalid input",
message:"\(title) cannot be empty",
preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss",
style: UIAlertActionStyle.Default)
self.presentViewController(alertController, animated: true, completion: nil)
return false
} else {
return true
}
}
And I call this function in the acceptButton action:
#IBAction func acceptButton(sender: UIButton){
if(checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority)
{
//if not empty, confirm the user input
// ...
}
When I run it, the alert message works fine but for some reason the console shows this:
2015-08-03 12:11:50.656 FinishItToday[13777:688070] >'s window is not equal to
's view's window!
Can anyone tell me why this warning appears? Thank you very much!
PS.
What I want it to do is that if any of the text field is empty, show the alert and then stay at the same page. If none of them are empty, then perform the segue and switch to another view. The code above works fine except the warning.
Here is your working code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var eventName: UITextField!
#IBOutlet weak var Priority: UITextField!
#IBAction func acceptButton(sender: UIButton){
if checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority){
println("Both Text Fields Are Empty")
}
}
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
var Alert = UIAlertController(title: "Invalid input", message: "\(title) cannot be empty", preferredStyle: UIAlertControllerStyle.Alert)
Alert.addAction(UIAlertAction(title: "Dismiss", style: .Cancel, handler: { action in
println("Click of cancel button")
}))
self.presentViewController(Alert, animated: true, completion: nil)
return false
} else {
return true
}
}
}
Use this code to for alert view controller in swift. It may help you.
import UIKit
protocol alertViewDelegate {
func actionActive(index:Int, tag:Int)
}
class AlertView: NSObject {
var delegate:alertViewDelegate!
func showAlert(title:String, message:String, actionName:NSArray, tag:Int) -> UIAlertController {
var alertController:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
for str: AnyObject in actionName {
let alertAction:UIAlertAction = UIAlertAction(title: str as! String, style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.delegate.actionActive(actionName.indexOfObject(str), tag:tag)
})
alertController.addAction(alertAction)
}
return alertController;
}
}