What Completion Handlers Should I Write? - ios

Having just learned about how to create completion handlers, I understand them in principle, but I don't know how to put the ideas into practice to accomplish what I need.
I have the following general code and storyboard structure: sessionVC (a UIViewController) and its UIView hold a container view with an embed segue to animationVC (also a UIViewController) and its SKView.
From sessionVC I want to run a series of animations in animationVC’s SKView. I’d like to prepare each animation as soon as possible (e.g., while each prior animation is still running), and I’d like each animation to wait for the last one to finish before it begins. See the code, below.
My question is, what do I put in place of the ???s in my code to accomplish the effects that I want (mentioned above, as well as after each *** in the code comments)?
// TO DELEGATE ANIMATION TO animationVC
protocol AnimateContentDelegate: AnyObject {
prepareContent(_ Content, contentWasPrepared: ???)
animateContent(animationDidComplete: ???)
playAnimation()
pauseAnimation()
}
// CONTROL THE OVERALL SESSION
class sessionVC: UIViewController {
// INITIALIZE contentArray
weak var delegate: AnimateContentDelegate?
override func viewDidLoad {
super.viewDidLoad()
delegate = self.childViewControllers[0] as? AnimateContentDelegate
runSession(contentArray)
}
func runSession(_ contentArray) {
for content in contentArray {
delegate?.prepareContent(content, contentWasPrepared: ???)
// ***DON’T START THE NEXT ANIMATION UNTIL contentWasPrepared
// DO CONTINUE THE CURRENT ANIMATION, AND ALLOW INTERACTIONS
delegate?.animateContent(animationDidComplete: ???)
// ***DON’T START THE NEXT ANIMATION UNTIL animationDidComplete
// DO CONTINUE THE CURRENT ANIMATION, AND ALLOW INTERACTIONS
}
}
#IBAction func playOrPause(_ sender: UILongPressGestureRecognizer) {
if sender == .possible || sender.state == .ended {
delegate?.playAnimation()
} else if sender.state == .began {
delegate?.pauseAnimation()
}
}
}
// PREPARE AND ANIMATE CURRENT CONTENT
class animationVC: UIViewController, AnimateContentDelegate {
// SET UP SKVIEW
func prepareContent(_ content: Content, prepCompleteHandler: ???) {
// PREPARE THE CONTENT
// ***REPORT WHEN IT IS FINISHED
}
func animateContent(animationCompleteHandler: ???) {
// ANIMATE THE CONTENT
// ***REPORT IF IT RAN TO COMPLETION
}
func playAnimation() {
skView?.scene?.isPaused = false
}
func pauseAnimation() {
skView?.scene?.isPaused = true
}
}

Following the usual conventions, you would declare the functions with a slightly different wording:
prepareContent(_ Content, completionHandler: { () -> Void })
animateContent(completionHandler: { () -> Void })
Then, below SET UP SKVIEW, you would call the delegate functions like this:
delegate?.prepareContent(content, completionHandler: {
self.delegate.animateContent(completionHandler: {
// do whatever needs to be done upon completion
})
})
(You could as well use the trailing closure syntax.)
By nesting the delegate calls like this you make sure animation is performed only after preparation has been finished.
The function bodies would look like this:
func prepareContent(_ content: Content, completionHandler: (() -> Void)) {
// ...
completionHandler()
}
func animateContent(completionHandler: (() -> Void)) {
// ...
completionHandler()
}
And if you prefer to have optional completion handlers, i. e. those which can be nil, change the function like this:
func prepareContent(_ content: Content, completionHandler: (() -> Void)?) {
// ...
completionHandler?()
}

It should look something like :
func prepareContent(_ content: Content, completionHandler: #escaping (Bool, Any?) -> Void) -> Void {
var finished : Bool = false
var output : Any? = nil
// prepare the content
// possibly update the output
//when ready :
finished = true
completionHandler(finished, output)
}
Then you use it :
prepareContent(content: content, completionHandler: { (f, o) in
if f {
if o != nil {
//do something
}
//else handle error
}
//else handle error
})
You can adapt it to your needs, adding more or less outputs if needs, error logs, and so on.

Do you want something like that ?
protocol AnimateContentDelegate: AnyObject {
func prepareContent(content : Content, contentWasPrepared: ((Bool) -> Void)?)
func animateContent(animationDidComplete: ((Bool) -> Void)?)
func playAnimation()
func pauseAnimation()
}
class YourObject : AnimateContentDelegate {
func prepareContent(content: Content, contentWasPrepared: ((Bool) -> Void)?) {
// prepare content
}
func animateContent(animationDidComplete: ((Bool) -> Void)?) {
// animate content
}
func playAnimation() {
}
func pauseAnimation() {
}
}
Use it like that :
let yourObject = YourObject()
yourObject.animateContent(animationDidComplete: { finished in
// animation did complete
})

Related

Safety way to use [weak self] when using TableViewCell delegate function with closure?

I'm trying to use tableviewcell delegate function with completion closure block.
I concern when I should use [weak self] in this situation.
And I also want to know the better way to implement this kind of logic
What to do this code?
when the user tapped to add new items into stack view
It's going to fetch data from a remote server.
If fetched then let tableviewcell to add new items
protocol myTableViewCellSubViewDelegate {
func fetchData(cell: MyTableViewCell, completion: #escaping (Bool) -> ())
}
class MyTableViewCell: UITableViewCell {
var delegate: myTableViewCellSubViewDelegate?
var stackView = UIStackView()
func startFetchData(){
delegate?.fetchData(cell: self){ success in
if success {
self.stackView.addArrangedSubview(UIView())
}
}
}
}
Look at startFecthData function in MyTableViewCell..
Should I use [weak self] or not?
in fetchData function, should I #escaping or not?
How about using defer?
Here is MyViewController.swift
class MyViewController: UIViewController, myTableViewCellSubViewDelegate {
func doSomthing(url : URL, completion: (Error?) -> ()) {
completion(nil)
}
func fetchData(cell: MyTableViewCell, completion: #escaping (Bool) -> ()) {
doSomthing(url: URL(string: "www.stackoverflow.com")!) { error in
if error != nil {
print("Error...")
}else {
completion(true)
}
}
}
}
You should use [weak self]. this will prevent a crash when this cell has been deallocated.
func startFetchData(){
delegate?.fetchData(cell: self){ [weak self] success in
if success {
self?.stackView.addArrangedSubview(UIView())
}
}
}
Use #escaping, since you run this closure in a different closure.
Don't.

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()
}
}

Swift 3 pass data through completion with userID

I'm trying to run a check on Firebase to see if a user exists, then I need to check for specific vales before continuing. I currently have this:
func myFirebaseNetworkDataRequest(finished: () -> Void) {
if let user = FIRAuth.auth()?.currentUser {
self.userUUID = (user.uid)
getUser(userUUID: self.userUUID)
finished()
}
}
In my view did load:
myFirebaseNetworkDataRequest {
// perform further operations here after data is fetched
if AppState.sharedInstance.user == true {
//present 1st view controller
} else {
//present 2nd view controller
}
In my "getUser" function:
func getUser(userUUID: String) {
let userFacebookRef = FIRDatabase.database().reference(withPath: "users").child(userUUID)
//The rest of the Firebase function.
AppState.sharedInstance.user == results.active
//active is = to true
What currently happens is that if presents the 2nd view controller because firebase hasent finished yet. I realize I need a block because firebase is already asnyc but how do I send userUUID through the closure/block?
You can have your closure parameter have an parameter of it's own. Something like,
func myFirebaseNetworkDataRequest(finished: (_ isAuthenticated: Bool) -> Void)
Then in your viewDidLoad, you would make your request...
open override func viewDidLoad() {
self.myFirebaseNetworkDataRequest( (isAuthenticated) -> {
if isAuthenticated {
// Present VC1
} else {
// Present VC 2
}
}
}

Sending a function to another function as a parameter

I want to send a func as a parameter to another func but I know how to do it only in case the func being sent has an input and an output parameters:
aFunc (sentFunc: Int -> String) {
...
}
But I want to achieve something like the following code where the function being sent does not have parameters:
func firstFunc(<i want to declare paramenter for a function here that itself does not have any parameters>) {
if (servicedCities != nil) {
<execute sent fuction>
} else {
objectMapper().getServicedCitiesifAvailable()
<execute sent fuction>
}
}
the func calling the above func should look like
func secondFunc() {
func myNestedFunction() {
....
}
....
firstFunc(myNestedFunction())
....
}
func firstFunc(passedFunc:()->()) {
passedFunc()
}
func secondFunc() {
func myNestedFunction() {
print("hi")
}
firstFunc(myNestedFunction)
}
secondFunc()
will ouput
hi
in the context of the firstFunc call.
Note that you have to omit the () in the last line after the myNestedFunction because you dont want to call the method but pass the method itself.
func firstFunc(sentFunc: () -> ()) {
sentFunc()
}
or
func firstFunc(sentFunc: Void -> Void) {
sentFunc()
}
I think you are looking for Swift closures.
Check this Swift closures and functions
func someFunc(paramFunc: () -> ()) {
paramFunc()
}
...
self.someFunc { () -> () in
//Do some code here;
}

block in Swift : return error " is not convertible to "

I made a mistake but I cannot see how to solve it. I would like to load all the assets from GameScene and send a Bool in a completion method. I use typealias : should it be renamed twice for the two files (gameScene and gameController)?
Then I have got an error on this line GameScene.loadSceneAssetsWithCompletionHandler{ :
((Bool) -> Void) is not convertible to 'GameScene'
Here is the code :
//gameController:
typealias OnComplete = (Bool) -> ()
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler{ (success:Bool)->Void in
println("2/ yes")
return
}
//gameScene : rewrite typealias?
typealias OnComplete = (Bool) -> ()
func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
self.loadSceneAssets()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("1/ yes")
completion(true)
})//main
})//global
}
I read some threads that said to add a "return", but it does not solve the error here.
Thanks
It's almost working, but you've got a couple things going wrong here. First of all, you can't redeclare a typealias. Second of all you're calling loadSceneAssetsWithCompletionHandler as a class function when it's set up as an instance function. Note changes:
typealias OnComplete = (Bool) -> ()
class GameController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler { success in
println("2/ yes")
return
}
}
}
class GameScene: UIViewController {
func loadSceneAssets() {
}
class func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let gameScene = GameScene()
gameScene.loadSceneAssets()
dispatch_async(dispatch_get_main_queue()) {
println("1/ yes")
completion(true)
}
}
}
}

Resources