UIViewController dismiss issue - ios

I want to wait until dismiss animation completes but I don't want to use many blocks in my code, so I wrote this function in UIViewController extension (almost like this worked several years ago for me):
func dismissAnimated() {
var comleted: Bool = false
self.dismiss(animated: true) {
comleted = true
}
while !comleted {
RunLoop.current.run(mode: RunLoop.Mode.common, before: Date.distantFuture)
}
}
so now instead of:
viewController.dismiss(animated: true) {
// code after completion
}
I was supposed to write:
viewController.dismissAnimated()
// code after completion
But it doesn't dismiss view controller and doesn't enter into completion block.
I tried different RunLoop modes, tried different dates, tried inserting RunLoop.current.run into while condition, it didn't work. Any ideas how to accomplish this?
Edit:
And it worked on iOS 9 or something like this (may be with some code changes, because I can't find my source code). I start RunLoop.current.run to avoid blocking main thread. For instance, if I put completed = true in DispatchQue.main.asyncAfter , it will work, the issue is with dismiss

I tried again because I was curious and this solution actually works for me:
#objc private func dismissTapped() {
let dismissalTime = dismissAnimated()
print("Dismissal took: %ld", abs(dismissalTime))
}
private func dismissAnimated() -> TimeInterval {
let startDate = Date()
var completed = false
self.dismiss(animated: true) {
completed = true
}
while !completed {
RunLoop.current.run(mode: .default, before: .distantFuture)
}
return startDate.timeIntervalSinceNow
}
iOS 12.1.2 | Swift 4.2

It doesn't dismiss because your while !completed loop has stalled the main thread and the UI update happens on the main thread. What is wrong with running whatever code you need to run inside the dismiss completion closure?
self.dismiss(animated: true) {
runSomeCode()
}

If it's really just about not using blocks maybe this may be a solution?
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if self.navigationController?.isBeingDismissed ?? self.isBeingDismissed {
print("Dismissal completed...")
}
}

Related

Swift dismissing view with clunky animation

I simply have a ViewController that I would like to dismiss. And this is my dismissAction:
#objc private func dismissView(){
self.dismiss(animated: true, completion: nil)
UserDefaultsService.shared.updateDataSourceArrayWithWishlist(wishlist: self.wishList)
let dataSourceArray = UserDefaultsService.shared.getDataSourceArray()
// update datasource array in MainVC
self.dismissWishlistDelegate?.dismissWishlistVC(dataArray: dataSourceArray, dropDownArray: self.dropOptions, shouldDeleteWithAnimation: false, wishlistToDelete: self.wishList)
}
Proble:
The dismiss animation is very clunky and not fluent at all. I found out that if I remove everything in the function but only call self.dismiss it is working perfectly fine. What is the issue here? Any idea on how I can fix this?
You can try to light-weight load in main thread by
DispatchQueue.global().async {
UserDefaultsService.shared.updateDataSourceArrayWithWishlist(wishlist: self.wishList)
}
And instead of let dataSourceArray = UserDefaultsService.shared.getDataSourceArray() use self.wishList directly in the last line

Why RxSwift Subscribe just run once in First launch viewWillAppear?

I write a subscribe in viewWillAppear.
But it also run once in first launch app.
When I push to another viewcontroller, I use dispose().
Then I back in first viewcontroller, my subscribe func in viewWillAppear don't run.
What's wrong with my rx subscribe?
var listSubscribe:Disposable?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
listSubscribe = chatrooms.notifySubject.subscribe({ json in
print("*1") //just print once in first launch
self.loadContents()
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let controllers = tabBarController?.navigationController?.viewControllers
if (controllers?.count)! > 1 {
listSubscribe?.dispose()
}
}
RxSwift documentation says "Note that you usually do not want to manually call dispose; this is only an educational example. Calling dispose manually is usually a bad code smell."
Normally, you should be doing something like this -
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
whatever.subscribe(onNext: { event in
// do stuff
}).disposed(by: self.disposeBag)
}
As for your question, I believe you don't need to re-subscribe because you subscription will be alive and 'notifySubject' will send you updates whenever there are any.
Maybe you can get some reactive implementation of viewWillAppear and similar functions? And forget about manual disposables handling... For example your UIViewController init will contain something like this:
rx.driverViewState()
.asObservable()
.filter({ $0 == .willAppear })
.take(1) // if you need only first viewWillAppear call
.flatMapLatest({ _ in
// Do what you need
})
And the implementation of driverViewState:
public extension UIViewController {
public enum ViewState {
case unknown, didAppear, didDisappear, willAppear, willDisappear
}
}
public extension Reactive where Base: UIViewController {
private typealias _StateSelector = (Selector, UIViewController.ViewState)
private typealias _State = UIViewController.ViewState
private func observableAppearance(_ selector: Selector, state: _State) -> Observable<UIViewController.ViewState> {
return (base as UIViewController).rx
.methodInvoked(selector)
.map { _ in state }
}
func driverViewState() -> Driver<UIViewController.ViewState> {
let statesAndSelectors: [_StateSelector] = [
(#selector(UIViewController.viewDidAppear(_:)), .didAppear),
(#selector(UIViewController.viewDidDisappear(_:)), .didDisappear),
(#selector(UIViewController.viewWillAppear(_:)), .willAppear),
(#selector(UIViewController.viewWillDisappear(_:)), .willDisappear)
]
let observables = statesAndSelectors
.map({ observableAppearance($0.0, state: $0.1) })
return Observable
.from(observables)
.merge()
.asDriver(onErrorJustReturn: UIViewController.ViewState.unknown)
.startWith(UIViewController.ViewState.unknown)
.distinctUntilChanged()
}
}

CNContactViewController Cancel Button Not Working

I'm trying to use the built-in new contact UI and am getting unexpected behavior with the cancel button. The code below works and calls up the new contact screen but the cancel button will only clear the screen entries not cancel out of the new contact screen. In the built in contacts app hitting cancel returns to the contact list screen. I would like the cancel button to close out the window.
#IBAction func newTwo(sender: AnyObject) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let npvc = CNContactViewController(forNewContact: nil)
npvc.delegate = self
self.navigationController?.pushViewController(npvc, animated: true)
}
}
}
did you implement CNContactViewControllerDelegate methods?
Here's a link to documentation
for example:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
It worked for me using the following code:
Swift 3
func contactViewController(_ vc: CNContactViewController, didCompleteWith con: CNContact?) {
vc.dismiss(animated: true)
}
Also I changed the way I was calling the controller:
Instead of:
self.navigationController?.pushViewController(contactViewController, animated: true)
the solution was:
self.present(UINavigationController(rootViewController: contactViewController), animated:true)
I found the solution using the example code Programming-iOS-Book-Examples written by Matt Neuburg:
Better way to do the dismissing would be to check if the contact is nil and then dismiss it. The dismiss doesn't work if you've pushed the view controller from a navigation controller. You might have to do the following:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
if let contactCreated = contact
{
}
else
{
_ = self.navigationController?.popViewController(animated: true)
}
}

Swift and Socket.io: Event doesn't run anymore when re-open a controller

I'm beginner Swift and Socket.io. The socket.io library for Swift that I'm using is https://github.com/nuclearace/Socket.IO-Client-Swift
For using 1 socket.io connection in a multiple controllers, I use Singleton with below code in Singleton_SocketManager.swift:
import Foundation
class Singleton_SocketManager {
static let sharedInstance = Singleton_SocketManager()
let socket = SocketIOClient(socketURL: "localhost:3000")
var currentController = ""
var loadedHander = [String: Bool]()
var currentRoomId = 0
init() {
loadedHander["ViewController"] = false
loadedHander["NextViewController"] = false
}
}
let sharedSocket = Singleton_SocketManager()
To add event handler in each controller, I call addHandlers() function in viewDidLoad() and the code of addHandlers() like that:
For ViewController:
func addHandlers() {
if (sharedSocket.loadedHander["ViewController"]!) {
return;
} else {
sharedSocket.loadedHander["ViewController"] = true
}
sharedSocket.socket.on("eventInViewController1") {[weak self] data, ack in
println("View Controller: Event 1")
}
}
For NextViewController:
func addHandlers() {
if (sharedSocket.loadedHander["NextViewController"]!) {
return;
} else {
sharedSocket.loadedHander["NextViewController"] = true
}
sharedSocket.socket.on("eventInNextViewController1") {[weak self] data, ack in
println("NextViewController: Event 1")
}
}
They run fine in first time. But if I have to change controller with this code:
let storyBoard = self.storyboard
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("NextViewController") as! NextViewController
self.presentViewController(nextViewController, animated: false, completion: nil)
and go back to ViewController with the same code, the old event handler will not run. I know I can run it by remove the code of checking and marking "sharedSocket.loadedHander" but if I do like that, all events will run twice, and triple if I go NextViewController and back to ViewController again.
I tried to research a lot about this issue but can't find any solutions for this. If you work with Swift or Objective-C and Socket.io library before and resolved this issue, please help me.
Thank you very much for your reading!
Try to disconnect from the first ViewController before performing the segue to the next ViewController. Then, reconnect in the second ViewController.
In addition, you might want declare the socket constant globally.

UIProgressView as health bar and update when button pressed. Swift

So I started making a game and I wanted to use a progress bar as a health bar for many different things and I couldn't figure out why it wasn't updating at all. It did, however, update in the viewdidload function. Here is the code that I am using.
func updateHealthBars() {
hullHealthBar.setProgress(hullHealth/hullHealthMax, animated: true)
cannonsHealthBar.setProgress(cannonsHealth/cannonsHealthMax, animated: true)
sailsHealthBar.setProgress(sailsHealth/sailsHealthMax, animated: true)
crewHealthBar.setProgress(crewHealth/crewHealthMax, animated: true)
}
#IBAction func setSailButton(sender: AnyObject) {
if hullHealth > 0 {
hullHealth-=20
}
else if cannonsHealth > 0 {
cannonsHealth-=20
}
else if sailsHealth > 0 {
sailsHealth-=20
}
else if crewHealth > 0 {
crewHealth-=20
}
updateHealthBars()
}
If anybody knows what I need to do to update their health when I press the button, I would be very thankful because I have been trying to do this for a while now without success. I am using XCode 6.
In Swift, when assigning numbers using literals without decimals, it uses Int. ProgressView requires floats. Try the following:
func updateHealthBars() {
dispatch_async(dispatch_get_main_queue(), ^{
hullHealthBar.setProgress(hullHealth/hullHealthMax, animated: true)
cannonsHealthBar.setProgress(cannonsHealth/cannonsHealthMax, animated: true)
sailsHealthBar.setProgress(sailsHealth/sailsHealthMax, animated: true)
crewHealthBar.setProgress(crewHealth/crewHealthMax, animated: true)
});
}
#IBAction func setSailButton(sender: AnyObject) {
if hullHealth > 0.0 {
hullHealth-=20.0
}
else if cannonsHealth > 0.0 {
cannonsHealth-=20.0
}
else if sailsHealth > 0.0 {
sailsHealth-=20
}
else if crewHealth > 0.0 {
crewHealth-=20.0
}
updateHealthBars()
}
Make sure your properties are also floats:
var hullHealth: Float = 0 or var hullHealth = 0.0
I finally got this to work. So my original code worked. The actual problem was something to do with my button not being connected right or something even though everything else worked fine. I made a new button and put the same things in it and it worked fine. It was a problem with the button, not the thread or anything.

Resources