I've looked at different sources to find an answer to this and possibly tried several ways to reloadData() but no luck so far.
Here is my case:
I have a tabBar where I present the content in a tableView. When you go into that tab, I present a modal view if you are not purchased the content yet.
Let's say you have purchased the content. The modal view dismisses itself but tableview doesn't show anything unless you kill the app and re-run again. When you this, tableView reloads itself with the content. No problem there. But this is definitely problematic and I do lose users just because of this.
I do keep track of the purchase with a key like this:
private var hiddenStatus: Bool = UserDefaults.standard.bool(forKey:"purchaseStatus")
I set it to true on a successful purchase as shown below:
self.setNonConsumablePurchase(true)
And finally the code where I check if the key is set to "true" on purchase. I call this on the viewDidLoad but it doesn't make sense because it's been already called when user touched on this tabBar item.
if hiddenStatus == true {
DispatchQueue.main.async(execute: {
self.segmentioView.valueDidChange = { [weak self] _, segmentIndex in
switch self?.segmentioView.selectedSegmentioIndex {
case 0:
self?.observeAugustProducts()
self?.tableView.reloadData()
case 1:
self?.observeSeptemberProducts()
self?.tableView.reloadData()
default:
self?.tableView.reloadData()
}
}
})
}
So I'm looking for two things:
1- To keep everything as it is and tweak the code to make this work.
2- Alternative ideas to give users a better in-app purchase experience (I once used UIAlertViewController for in-app purchase but decided not to go with it)
I recommend using this extension:
extension NotificationCenter {
func setUniqueObserver(_ observer: AnyObject, selector: Selector, name: NSNotification.Name, object: AnyObject?) {
NotificationCenter.default.removeObserver(observer, name: name, object: object)
NotificationCenter.default.addObserver(observer, selector: selector, name: name, object: object)
}
}
Before dismissing your Modal View, have your Modal View's ViewController post a Notification.
Something like:
NotificationCenter.default.post(name: "Purchased", object: nil)
Then have your TableView's ViewController register to listen for that Notification (in viewWillAppear) - and handle it.
Something like:
NotificationCenter.default.setUniqueObserver(self, selector: #selector(handlePurchased), name: "Purchased", object: nil)
and
#objc func handlePurchased() {
// Reload your TableView, etc.
}
Hope this helps someone!
Related
In the main panel of my application I have a textView. When user presses it, the keyboard appears and I have to modify the constraints on this panel so that the keyboard doesn't overlay anything. For that purpose I attached an observer in viewDidLoad method of that class:
NSNotificationCenter.defaultCenter().addObserver(self, selector:
#selector(keyboardWillChangeFrame),
name: UIKeyboardWillChangeFrameNotification, object: nil)
and then I have a method:
func keyboardWillChangeFrame(notification: NSNotification) {
that handles everything.
Now, on different panel I want to modify my constraints in a different way, so I prepared a different method for that:
func keyboardWillChangeFrameInOtherWay(notification: NSNotification) {
and now to support that on that view, I have to attach it to the observer. But when I wrote in viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector:
#selector(keyboardWillChangeFrameInOtherWay), name:
UIKeyboardWillChangeFrameNotification, object: nil)
then when keyboard appears on the 2nd panel - both methods fire (from the 1st panel and the 2nd one). How can I run only one method for a specific panel?
I haven't clearly understand your question, btw you can access the object field of a NSNotification. If you pass an object to the NSNotificationCenter then you will receive it and you can perform one method or the other one. Another possibility is to remove and add every time the observer, so that the Controller that own the textField will observes only one per time...
I will post my answer since I discovered the problem here, I hope it's the best way of handling this issue. I added this piece of code on the first panel:
override func viewDidDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
also, since it's embedded in navigation controller, I moved this:
NSNotificationCenter.defaultCenter().addObserver(self, selector:
#selector(keyboardWillChangeFrame),
name: UIKeyboardWillChangeFrameNotification, object: nil)
to viewWillAppear and now it works fine.
In my application, once the user logs in from the LoginViewController, he is directed to the ProfileTabBarController.
ProfileTabBarController is a subclass of UITabBarController.It consists of three view controllers all of which need a reference to an instance of Profile.
When the ProfileTabBarController is pushed, it loads the user's profile. Once the profile loads successfully, it sets a reference to profile on each of its view controllers and then adds them as tab items.
The reason I chose this approach is that if the user launches the application and his token hasn't expired, the application should go directly to the ProfileTabController; the user need not login again. Rather than duplicate the code for loading the profile in both AppDelegate and LoginViewController, I felt it was better to encapsulate it in ProfileTabBarController.
The problem with this approach is that during the segue, the UITabBarController appears black as no view controller has been set. I managed to remedy this by creating a LoadingViewController and setting it initially as the only UIViewController of ProfileTabController
My question is whether there is a better approach to solving this problem. I don't really like the idea of having a UIViewController with no other purpose then to display a loading icon.
Presenting a tab view controller with no views doesn't make much sense, so I think your solution of giving it a view controller to handle this loading state makes perfect sense. Throw in some cool animation to keep the user entertained.
It's also possible that something could go wrong and the profile may not load properly. This transitional view controller seems like a good place to put the code to deal with possible errors.
I was able to eliminate the Loading ViewController by making use of NSNotificationCenter.
ProfileTabBarController adds the three ViewControllers and then begins loading the profile.
override func viewDidLoad() {
super.viewDidLoad()
self.setupUI()
self.setupViewControllers()
self.loadProfile()
}
Once loading completes, the ProfileTabBarController emits a ProfileDidLoad notification.
let completion = { (profile: Profile?) in
MBProgressHUD.hideHUDForView(self.view, animated: true)
if let profile = profile {
self.profile = profile
NSNotificationCenter.defaultCenter().postNotificationName(Notification.ProfileDidLoad, object: self.profile)
} else {
UIAlertView(
title: "",
message: NSLocalizedString("Error loading profile", comment: ""),
delegate: nil,
cancelButtonTitle: "OK"
).show()
}
}
The ViewControllers observe the ProfileDidLoad notification:
func setupNotificationObserver(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "profileDidLoad:", name: Notification.ProfileDidLoad, object: nil)
}
#objc func profileDidLoad(notification: NSNotification){
if let profile = notification.object as? Profile{
self.profile = profile
}
}
In this way, the view controllers are immediately displayed after login without the need of a loading screen.
I have a button in my ViewController.swift:
#IBOutlet weak var exampleButton: UIButton!
I would like to show/hide that button from the AppDelegate, when something specific happens (i.e. the app enter background etc.).
How can i do that?
One Approach can be
- You can use Notifications for this
Add Observer in your view controller where button needs to be hidden
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "hideButton",
name: #"HIDE_BUTTON_NOTIFICATION",
object: nil)
func hideButton() -> Void {
// Hide your button here
// Remember to hide on main thread
}
From wherever you want to hide the button (like from AppDelegate), you can post this notification
NSNotificationCenter.defaultCenter().postNotificationName(#"HIDE_BUTTON_NOTIFICATION", object: nil)
Rather than let another object directly manipulate it, use a notification to indicate a state change. The state dictates whether the button appears or not.
Make sure in your notification listener that you only touch the button on the main thread.
Notification is a great idea, but what if your ViewController is not your initial ViewController, or hasn't been initialized yet? It will not be able to catch this notification. Possible solution (maybe not elegant) in extension to other answers is to provide a flag. Your ViewController will be checking it, e.g. in viewDidLoad(). Your flag could be stored in a singleton object which will be catching notification from AppDelegate.
To sum up, you should add notification observer in your ViewController, to catch event from AppDelegate. (like in other answers)
Create singleton class to store appropriate information.
Check condition in viewDidLoad:
if YOUR_SINGLETON.YOUR_FLAG == true {
showButton()
} else {
hideButton()
}
Don't forget to add notification observer also in your singleton class.
i am fairly new to ios programming and i am not sure what the best way to do this is. i want to Create a delegate protocol that can be used to notify interested observers (two viewcontrollers to be specific) of an image array's change in state. i want each view controller to conform to it also. for example, if it is in state 1 then it only appears in the first viewcontroller and if it is in state 2 then it only appears in the second viewcontroller.
this is what i have so far:
protocol imgState {
static var state1 : Int {get set}
static var state2 : Int {get set}
func stateChange(){
if state1 == 1 {
set {
state1 == 0
state2 == 1
}
}
if state2 == 1 {
set {
state1 == 1
state2 == 0
}
}
}
}
my thinking is to call the function in each viewcontroller when a button is pressed to change the state.
is there any good tutorials someone could direct me towards or any guidance on how to start at all? anything should help.
I've used delegates in the past but I much prefer posting a notification.
In your example, when the button is pressed, you can do something like this,
NSNotificationCenter.defaultCenter().postNotificationName("buttonPressed", object: nil)
The other views would listen for this notification with:
// Usually added to viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateState", name: "buttonPressed", object: nil)
You would then have a func named "updateState" in the relevant view controllers which would be called whenever the 'buttonPressed' notification is posted.
Hope this helps.
You can achieve this in a much simpler way using NSNotifications... The way you would do it, since you're trying to listen to 2 different inputs/actions, is to post a notification for each... these will respond right away!
So as an example, in an action method (but could be used anywhere really) we could be expecting 2 different values...
var var1:Bool = false // setting a false value as a default
#IBAction func expectAction(sender:AnyObject) {
if var1 == false {
NSNotificationCenter.defaultCenter().postNotificationName("key1", object: nil)
} else {
NSNotificationCenter.defaultCenter().postNotificatioinName("key2", object: nil)
}
}
Then on each of your classes you would add observers to respond to such notifications as follows:
(assuming ViewController1 is listener 1 and ViewController2 is listener 2)
At your viewDidLoad() method in ViewController1 add the following:
NSNotificationCenter.defaultCenter().addObserver(self, selector:"actonKey1:", name:"key1", object: nil)
Then add a new method in ViewController1
#obj private func actonKey1(notification: NSNotification) {
// do your work here
}
Finally (for this ViewController1) add a deinitializing method like this:
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
You repeat this for ViewController2 changing the appropriate names of the key1 to key2
At your viewDidLoad() method in ViewController2 add the following:
NSNotificationCenter.defaultCenter().addObserver(self, selector:"actonKey2:", name:"key2", object: nil)
Then add a new method in ViewController2
#obj private func actonKey2(notification: NSNotification) {
// do your work here
}
Finally (for this ViewController2) add a deinitializing method like this:
deinit{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
And that's pretty much it... The response time is amazingly fast... and it is (at least to my belief) less convoluted than using protocols and delegates...
Cheers
One idea is to use an Observable/Subject class (like this one that I wrote: https://gist.github.com/dtartaglia/0a71423c578f2bf3b15c but there are lots of libraries that provide this, and other, services.)
With this service, you would wrap the image in a Subject object and the two view controllers would attach a block of code to the subject. Then whenever the contained image is changed (by assigning to mySubject.value) the attached blocks will be called.
Just be sure to dispose of the observers when done.
I have an app that use http calls to stream video from external storage.
When the user's device isn't connected to a network service, I need the app to go back to the previous controller.
the flow is the following: the user access a list of elements (table view cell), select one, then the app goes to the player controller. On this controller, the call is made to stream the file.
I use an api call handler in a class outside of the controller and I don't know how to proceed to make it go back to the previous controller from here (the element list).
Connectivity issues errors are all catched within the api class.
I don't show any code as Im not sure it would be relevant. If you need to see anything, let me know and I will update the question. What should be the way to do that? (of course I use a navigation controller)
Thanks
If you want go back to the previous view controller you should use:
navigationController?.popViewControllerAnimated(true)
If you need to use this function not in the view-controller but in another class you can use NSNotificationCenter for notify the view-controller when it's needed to show the previous controller, just like this:
YourViewController
override func viewDidLoad()
{
...
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "goBack:",
name: "goBackNotification",
object: nil)
...
}
func goBack(notification: NSNotification)
{
navigationController?.popViewControllerAnimated(true)
}
AnotherClass
NSNotificationCenter.defaultCenter().postNotificationName("goBackNotification", object: nil)
Don't forget to remove the observer in your YourViewController:
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
EDIT 1: you can use obviously a delegate instead of a NSNotification method. If you don't know the differences between NSNotification and delegate I recommend to you this answer.
A common approach besides NSNotificationCenter is to utilize closures or delegates to inform your ViewController that the streaming attempt failed. Using closures, the API of the class responsible for the streaming could be extended to take a completion closure as parameter, and call it with an NSError, if one occurred, or nil if it didn't.
func streamFile(completion: NSError? -> Void) {
// Try to start the streaming and call the closure with an error or nil
completion(errorOrNil)
}
When the call to the API in the ViewController is made you can then pass a closure to the method and check for errors. In case something went wrong an error should be present and the ViewController should be dismissed
func startStream() {
StreamingAPIClient.streamFile(completion: { [weak self] error in
if error != nil {
// Handle error
self.dismissViewControllerAnimated(true, completion: nil)
} else {
// Proceed with the streaming
}
})
}