Essentially I'm trying to creating a function that sets up multiple notifications and while it does this, I want it to display a loading/progress bar in an alert view and update this while it sets up the notifications.
The problem is that the UI is not updating when I'm adding the progress view to the alert view as a subview, until the processing is complete.
Usually I fix this kind of problem with 'DispatchQueue.main.async'. But this doesn't seem to make a difference.
Below is the code. In the 'PresentLoadingView' function, the processing in the 'SetUpNotifications' function that is passed as the 'viewPresented' closure function finishes before the UI actually shows that the 'progressView' is added to the 'loadingView'.
I'm pretty sure this is a threading issue as it doesn't seem to reach the main UI thread when using 'DispatchQueue.main.async', possibly something to do with it having an #escaping parameter? Or just the fact that it is a closure within a closure?
var centre: UNUserNotificationCenter?
func AttemptToSetUpNotifications() {
centre = UNUserNotificationCenter.current()
centre!.requestAuthorization(options: [.alert, .badge], completionHandler: SetUpNotificationsChecks)
}
func SetUpNotificationsChecks(granted: Bool, error: Error?) {
if error != nil {
return
}
if !granted {
return
}
PresentLoadingView(viewPresented: SetUpNotifications)
}
var loadingView: UIAlertController?
func PresentLoadingView(viewPresented: #escaping ((UIProgressView)-> Void)) {
loadingView = UIAlertController(title: "Setup Notifications", message: "Setting up notifications, please wait...", preferredStyle: .alert)
self.present(loadingView!, animated: true, completion: {
// Add your progressbar after alert is shown
let margin:CGFloat = 8.0
let rect = CGRect(x:margin, y:72.0, width:self.loadingView!.view.frame.width - margin * 2.0 , height:2.0)
let progressView = UIProgressView(frame: rect)
progressView.progress = 0.0
progressView.tintColor = Common.CommonTintColor
self.loadingView!.view.addSubview(progressView)
// DispatchQueue.main.async {
// self.loadingView!.view.addSubview(progressView)
// }
viewPresented(progressView)
})
}
Any help would be greatly appreciated, sorry if the problem is my lack of concurrency knowledge, and if you need anymore info, please ask.
Thanks.
Related
I have an app that can download many publications from a server at once. For each publication that already exists in the app, I want to prompt the user if he wants to overwrite the existing version.
Is there any clean way to present UIAlertControllers so that when the user has answered one, the app presents the next one?
Here is the output
Though two alert actions were called in a subsequent statements, second alert will be shown only after user interacts with alert on screen I mean only after tapping ok or cancel.
If this is what you want, as I mentioned in my comment you can make use of Asynchronous Operation and Operation Queue with maximum concurrent operation as 1
Here is the code.
First declare your own Asynchronous Operation
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
}
}
Though code looks very complicated in essence its very simple. All that you are doing is you are overriding the isFinished and isAsynchronous properties of Operation.
If you know how Operation queues works with Operation it should be very clear as to why am I overriding these properties. If in case u dont know! OperationQueue makes use of KVO on isFinished property of Operation to start the execution of next dependent operation in Operation queue.
When OperationQueue has maximum concurrent operation count as 1, isFinished flag of Operation decides when will next operation be executed :)
Because user might take action at some different time frame on alert, making operation Asynchronous (By default Operations are synchronous) and overriding isFinised property is important.
AlertObject is a convenience object to hold alert's meta data. You can modify it to match your need :)
Thats it. Now whenever whichever viewController wants to show alert it can simply use MyAsyncOperation make sure you have only one instance of Queue though :)
This is how I use it
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)
As you can see I have two alert operations added in subsequent statement. Even after that alert will be shown only after user dismisses the currently displayed alert :)
Hope this helps
Althought answer with Queue is very good, you can achieve te same as easy as:
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)
}
(replace array of messages with whatever you want)
I would recommend creating a Queue data structure (https://github.com/raywenderlich/swift-algorithm-club/tree/master/Queue).
Where alert objects are queued in the order that the alerts are initialized. When a user selects an action on one of the alerts, dequeue the next possible alert and present it.
I had the same problem in my app and tried several solutions, but all of them were messy. Then I thought of a very simple and effective way: use a delay to retry presentation until it can be shown. This approach is much cleaner in that you don't need coordinated code in multiple places and you don't have to hack your action handlers.
Depending on your use case, you might care that this approach doesn't necassarily preserve the order of the alerts, in which case you can easily adapt it to store the alerts in an array to preserve order, showing and removing only the first on in the array each time.
This code overrides the standard presentation method in UIViewController, use it in your subclass which is presenting the alerts. This could also be adapted to an app level method if needed that descends from the rootViewController to find the top most presented VC and shows from there, etc.
- (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];
}
}
Few years ago I wrote a kind of presentation service, that process queue of items to present. When there isn't any presented view in current moment it take another one from array of items to present.
Maybe it will help someone:
https://github.com/Flawion/KOControls/blob/master/Sources/KOPresentationQueuesService.swift
Usage is very simple:
let itemIdInQueue = present(viewControllerToPresent, inQueueWithIndex: messageQueueIndex)
Problem:
I am currently facing a problem with developing an iOS Mobile Application in Swift that utilizes:
BTLE: Connecting to a peripheral device and sending/receiving data to/from it.
Networking: If the peripheral is connected to a network (wireless and/or ethernet), then the communication over BTLE "could" instead happen over the network.
Model-View-ViewModel architecture
RxSwift
About the App:
It starts with a Bluetooth Setup view, which walks the user through the process of pairing with the peripheral device (disjoint from the TabBarController).
After successfully pairing with the device, all configuration is requested by the iOS App from the device, which is sent as JSON.
This JSON contains the different Model information (programming) that the App displays to the user for manipulation and needs to be stored in a array somehow in a Singleton manor to where a view-model can request any index for displaying to the user.
After all the data is received, the Bluetooth View dismisses and the TabBarView's are presented.
Current Examples:
A good example to relate this App to would be the Apple Watch and the correlating iOS App that allows you to configure everything. I am having to do somewhat the same concept.
Another good example app from this blog post where they are doing something similar to what I am trying to achieve. The difference I am running into though, is their dependency injection setup for MVVM (as well as other similar examples). I've used a storyboard, where as they have programmatically instantiated their view controllers in the AppDelegate.
And my problem...
How can I pass the data (efficiently) from BluetoothView to TabBarView without NSNotifications or PrepareForSegues? Keeping in mind that I am intending to use the library RxSwift for asynchronous event handling and event/data streams. I am trying to keep this App as stateless as possible.
Are the Servers in this blog post a good practice for retrieving view-models and/or updating them?
I find that, when using RxSwift, the "view-model" ends up being a single pure function that takes observable parameters from the input UI parameters and returns observables that are then bound to the output UI elements.
Something that really helped me wrap my head around Rx was the tutorial videos for cycle.js.
As for your specific conundrum...
What you are doing doesn't have to be "forward" movement. Look at it this way... The TabBarView needs some data, and it doesn't care where that data comes from. So give the TabBarView access to a function that returns an observable which contains the necessary data. That closure will present the Bluetooth View, make the connection, get the necessary data and then dismiss the Bluetooth View and call onNext with the required data.
Looking at this gist might help get across what I'm talking about. Granted the gist uses PromiseKit instead of RxSwift, but the same principle can be used (instead of fulfill, you would want to call onNext and then onCompletion.) In the gist, the view controller that needs the data simply calls a function and subscribes to the result (in this case, the result contains a UIImage.) It is the function's job to determine what image sources are available, ask the user which source they want to retrieve the image from and present the appropriate view controller to get the image.
The current contents of the gist are below:
//
// UIViewController+GetImage.swift
//
// Created by Daniel Tartaglia on 4/25/16.
// Copyright © 2016 MIT License
//
import UIKit
import PromiseKit
enum ImagePickerError: ErrorType {
case UserCanceled
}
extension UIViewController {
func getImage(focusView view: UIView) -> Promise<UIImage> {
let proxy = ImagePickerProxy()
let cameraAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.Camera) ? nil : UIAlertAction(title: "Camera", style: .Default) { _ in
let controller = UIImagePickerController()
controller.delegate = proxy
controller.allowsEditing = true
controller.sourceType = .Camera
self.presentViewController(controller, animated: true, completion: nil)
}
let photobinAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) ? nil : UIAlertAction(title: "Photos", style: .Default) { _ in
let controller = UIImagePickerController()
controller.delegate = proxy
controller.allowsEditing = false
controller.sourceType = .PhotoLibrary
self.presentViewController(controller, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
if let cameraAction = cameraAction {
alert.addAction(cameraAction)
}
if let photobinAction = photobinAction {
alert.addAction(photobinAction)
}
alert.addAction(cancelAction)
let popoverPresentationController = alert.popoverPresentationController
popoverPresentationController?.sourceView = view
popoverPresentationController?.sourceRect = view.bounds
presentViewController(alert, animated: true, completion: nil)
let promise = proxy.promise
return promise.always {
self.dismissViewControllerAnimated(true, completion: nil)
proxy.retainCycle = nil
}
}
}
private final class ImagePickerProxy: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let (promise, fulfill, reject) = Promise<UIImage>.pendingPromise()
var retainCycle: ImagePickerProxy?
required override init() {
super.init()
retainCycle = self
}
#objc func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = (info[UIImagePickerControllerEditedImage] as? UIImage) ?? (info[UIImagePickerControllerOriginalImage] as! UIImage)
fulfill(image)
}
#objc func imagePickerControllerDidCancel(picker: UIImagePickerController) {
reject(ImagePickerError.UserCanceled)
}
}
I've tried a whole bunch of ways to get Game Center working in my SpriteKit game. Unfortunately the way I've done it in the past using ObjC and ViewControllers don't work because I'm using SKScene/ a GameScene.
This is the swift version of the code (I think):
// MARK: Game Center Integration
//login and make available
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer()
print(localPlayer)
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if ((viewController) != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}else{
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
//submit a score to leaderboard
func reportScoreToLeaderboard(thisScore:Int){
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "LeaderboardID")
scoreReporter.value = Int64(thisScore)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: { (error: NSError?) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
print("Score submitted")
}
})
}
}
//show leaderboard (call from button or touch)
func showLeaderboard() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
//hides view when finished
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController){
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
Unfortunetely, no matter what I try, I either get this error:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
... or it just crashes.
I've read that NSNotifications can be used? But how?
I'm guessing the best way is to set it all up in the GameViewController.swift and use NSNotifications to communicate with the RootViewController from the GameScene? I can't seem to find a tutorial or example though.
Use delegation when a view controller needs to change views, do not have the view itself change views, this could cause the view trying to deallocating while the new view is being presented, thus the error you are getting
What I have
I don't understand the nature of the problem.
I have 2 View Controllers :
1) FeedViewController ,which shows events in tableview
2) EventViewController, pushed when you press on event.
When Feed Loads, it start asynchronous loadings of all images of all events.
It's done for each event by following function:
EventsManager().loadProfilePictureById(event.profilePictureID as String, currentProgress: event.profilePictureProgress, completionHandler: {
(progress:Double, image:UIImage!, error:NSError!) -> Void in
event.profilePictureProgress = progress
if image != nil {
event.profilePicture = image
}
if (error == nil){
if (self.tableView.headerViewForSection(index) != nil){
var header:eventHeaderView = self.tableView.headerViewForSection(index) as! eventHeaderView
header.updateProfilePicture(
self.eventsManager.events[index].profilePictureID as String,
progress: self.eventsManager.events[index].profilePictureProgress,
image: self.eventsManager.events[index].profilePicture)
}
}else{
println("Error:" + error.description)
}
})
Here is how I push EventViewController:
func PushEventViewController(sender:UITapGestureRecognizer)->Void{
let ViewSender = sender.view!
let selectedRow = ViewSender.tag
//let Cell:HomeEventTableViewCell = TimelineEventTable.cellForRowAtIndexPath(SelectedIndexPath) as HomeEventTableViewCell
dispatch_async(dispatch_get_main_queue(), {
() -> Void in
let VC:EventViewController = self.storyboard?.instantiateViewControllerWithIdentifier("EventViewController") as! EventViewController
VC.event = self.eventsManager.events[selectedRow]
self.navigationController?.pushViewController(VC, animated: true)
})
}
Problem
The problem is that if I press on event and push EventViewController before image is downloaded (completion handlers are still getting called) it crashes app.
Assumptions
I struggled with this for days and couldn't find a solution,
but my assumptions are that completion handler called after
Crash happens when it tries to execute following line after EventViewController was pushed:
event.profilePictureProgress = progress
if image != nil {
event.profilePicture = image
}
I assume when new view controller is pushed , event object which is used in completion handler is being deallocated
Found out where the problem was, the issue was that variable event.profilePictureProgress was declared as dynamic var (I was going to take advantage of that to add observer to it after).
I am needing to implement a progress bar that takes into account a couple of factors.
I have three different classes, my ViewController, a Networking class to handle the network calls and a dataManager class to handle all the db operations.
Now my progressView lives in my viewcontroller and I am looking at a way of updating it as each of the different operations are performed in the other classes.
I am using Alamofire so I know I can use .progress{} to catch the value of the JSON progress but that would also mean exposing the ViewController to the Networking class, which I assume is bad practice?
I think this should be achieved using completion handlers but as I have already setup another thread for handling the JSON / DB operation I'm not wanting to over complicate it anymore than I need to
Networking:
func makeGetRequest(url : String, params : [String : String]?, completionHandler: (responseObject: JSON?, error: NSError?) -> ()) -> Request? {
return Alamofire.request(.GET, url, parameters: params, encoding: .URL)
.progress { _, _, _ in
//bad practice?
progressView.setProgress(request.progress.fractionCompleted, animated: true)
}
.responseJSON { request, response, data, error in completionHandler(
responseObject:
{
let json = JSON(data!)
if let anError = error
{
println(error)
}
else if let data: AnyObject = data
{
let json = JSON(data)
}
return json
}(),
error: error
)
}
}
ViewController:
dataManager.loadData({(finished: Bool, error:NSError?) -> Void in
if let errorMessage = error{
self.syncProgress.setProgress(0, animated: true)
let alertController = UIAlertController(title: "Network Error", message:
errorMessage.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
if finished{
for i in 0..<100 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
sleep(1)
dispatch_async(dispatch_get_main_queue(), {
self.counter++
return
})
})
}
}
})
As you can see I am waiting on the finished boolean in the datamanger class to be set before updating the progress bar. The thing is, dataManager makes a call to networking and performs a bunch of other stuff before it finishes, it would be handy to update the progress bar along the way but I'm not sure of the best approach?
DataManager:
func loadData(completion: (finished: Bool, error: NSError?) -> Void) {
var jsonError: NSError?
networking.makeGetRequest(jobsUrl, params: nil) { json, networkError in
//....
}
I'm not too familiar with swift so I can't give you a code example but the way I would do this is create a protocol on your Networking class NetworkingDelegate and implement that protocol in your ViewController. The protocol method would be something like (in objective-c) NetworkingRequestDidUpdatProgress:(float progress)
This is assuming your ViewController calls Networking.makeGetRequest. If it's another class, you would implement the delegate in that class, or you could bubble up the delegate calls to your ViewController through the DataManager class.