Disable buttons of 1 view controller in another view controller - ios

I am having a problem when trying to disable button's user interaction of 1 view controller in another view controller.
I have searched similar questions here, but some seems outdated or does not work for me:
How to access an IBOutlet from another class.
My scenario is as follows:
class ViewControllerA() {
#IBOutlet weak var btnFirst: UIButton!
#IBOutlet weak var btnSecond: UIButton!
#IBOutlet weak var btnThird: UIButton!
override func viewDidLoad() {
var vcB = ViewControllerB()
vcB.closure = {
// Meet some condition, want to disable buttons of ViewControllerA here
}
}
}
class ViewControllerB() {
var closure: () -> Void = {}
// Do something with closure here
}
My problem is that i set the breakpoint in the closure and try to use directly IBOulet in closure to disable buttons like:
btnFirst.isUserInteractionEnabled = false
Or try to set a property of ViewControllerA in closure of ViewControllerB and use property observer, whenever this property changes, enable or disable buttons of ViewControllerA.
My problem is that, i can still click the buttons as if it's enable. Sorry, i cannot post the code, please help me!
Thanks

You can post notification from second view controller and add observer for that particular posted notification in first view controller.
In that observer method, you can do your stuff like disabling user interaction for first view controllers' button.

Since you did not post any relevant code, I can only guess what might have happened:
I assume that you are not accessing the btnFirst of the correct viewcontroller. In ViewControllerA.viewDidLoad you are creating an new Instance of ViewControllerB and set the closure. Are you also showing exactly this view controller's view? Or how will the user navigate to B? If you are using storyboard segues, those will create a new B instances and show its view. Now when you execute the closure in A, this will disable the button of the first B, not of the B that is displayed.
But this is still just a guess...

Related

How to prevent timer reset using pushViewController method?

I'm trying to keep a timer running even if I switch view controllers. I played around with the Singleton architecture, but I don't quite get it. Pushing a new view controller seems a little easier, but when I call the below method, the view controller that is pushed is blank (doesn't look like the view controller that I created in Storyboards). The timer view controller that I'm trying to push is also the second view controller, if that changes anything.
#objc func timerPressed() {
let timerVC = TimerViewController()
navigationController?.pushViewController(timerVC, animated: true)
}
You need to load it from storyboard
let vc = self.storyboard!.instantiateViewController(withIdentifier: "VCName") as! TimerViewController
self.navigationController?.pushViewController(timerVC, animated: true)
Not sure if your problem is that your controller is blank or that the timer resets. Anyway, in case that you want to keep the time in the memory and not deallocate upon navigating somewhere else I recommend you this.
Create some kind of Constants class which will have a shared param inside.
It could look like this:
class AppConstants {
static let shared = AppConstants()
var timer: Timer?
}
And do whatever you were doing with the timer here accessing it via the shared param.
AppConstants.shared.timer ...
There are different parts to your question. Sh_Khan told you what was wrong with the way you were loading your view controller (simply invoking a view controller’s init method does not load it’s view hierarchy. Typically you will define your view controller’s views in a storyboard, so you need to instantiate it from that storyboard.)
That doesn’t answer the question of how to manage a timer however. A singleton is a good way to go if you want your timer to be global instead of being tied to a particular view controller.
Post the code that you used to create your singleton and we can help you with that.
Edit: Updated to give the TimeManager a delegate:
The idea is pretty simple. Something like this:
protocol TimeManagerDelegate {
func timerDidFire()
}
class TimerManager {
static let sharedTimerManager = TimerManager()
weak var delegate: TimeManagerDelegate?
//methods/vars to manage a shared timer.
func handleTimer(timer: Timer) {
//Put your housekeeping code to manage the timer here
//Now tell our delegate (if any) that the timer has updated.
//Note the "optional chaining" syntax with the `?`. That means that
//If `delegate` == nil, it doesn't do anything.
delegate?.timerDidFire() //Send a message to the delegate, if there is one.
}
}
And then in your view controller:
//Declare that the view controller conforms to the TimeManagerDelegate protocol
class SomeViewController: UIViewController, TimeManagerDelegate {
//This is the function that gets called on the current delegate
func timerDidFire() {
//Update my clock label (or whatever I need to do in response to a timer update.)
}
override func viewWillAppear() {
super.viewWillAppear()
//Since this view controller is appearing, make it the TimeManager's delegate.
sharedTimerManager.delegate = self
}

Deallocate view controllers in navigation controller that have a reference to self

Say I have view controllers A, B, C, D & E all embedded in a navigation controller. In view controller B, I have a custom UIImageView object. In C, I have a custom UITextfield object. Both custom classes have a reference to the view controller for various reasons such as I have to perform things like segue when a user taps the image view. To accomplish this, I have this inside each custom class file:
var controller: UIViewController?
And then inside each view controller, inside viewDidLoad I set that variable to self and everything works as expected (segues on tap etc..)
I have an unwind segue from E back to A. However, I noticed that due to these custom objects in view controllers B & C, both were not being deallocated due to a retain cycle caused by having this reference to the view controller. I fixed the issue by setting the controller variable to nil upon segue, however this creates a problem such that if the user goes back (pops the current view controller), because I set the controller variable to nil upon segue, nothing works (it wont segue again because controller var = nil). I thought I might fix this by adding viewWillAppear code as follows:
override func viewWillAppear(_ animated: Bool) {
usernameTextField.controller = self
passwordTextField.controller = self
}
Because I read that viewWillAppear will be called each time the viewcontroller comes into view. This did not fix the problem.
Any ideas on how to go about this? How can I set the controllers to nil during the unwind maybe...?
As the other answers have said you need to make it a weak reference like this:
weak var controller: UIViewControler?
However I would go further and say that you should not be keeping a reference to to a UIViewController inside any UIView based object (UIImageView, UITextField, etc). The UIViews should not need to know anything about their UIViewControllers.
Instead you should be using a delegation pattern. This is a basic example:
1) Create a protocol for the custom UIImageField like this:
protocol MyImageFieldProtocol: class {
func imageTapped()
}
2) Then add a delegate like this:
weak var delegate: MyImageFieldProtocol?
3) Your UIViewController then conforms to the protocol like this:
class MyViewController: UIViewController, MyImageFieldProtocol {
}
4) Somewhere inside the view controller (viewDidLoad is usually a good place you assign the view controller to the image views delegate like this:
func viewDidLoad {
super.viewDidLoad()
myImageView.delegate = self
}
5) Then add the function to respond to the protocol action to the view controller like this:
func imageTapped {
self.performSegue(withIdentifier: "MySegue", sender: nil)
}
var controller: UIViewController? should be a weak reference. Like this:
weak var controller: UIViewController?
To know more about that read about Resolving Strong Reference Cycles Between Class Instances in Swift's documentation.
You should use weak references when you keep some ViewControllers
weak var controller: UIviewControler?
You should check everything link to retain cycle, and referencing in swift :
https://krakendev.io/blog/weak-and-unowned-references-in-swift
https://medium.com/#chris_dus/strong-weak-unowned-reference-counting-in-swift-5813fa454f30
I had similar issues, I advice you to look at those link : How can I manage and free memory through ViewControllers

Swift - Access IBOutlet in other class

I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}

Present subclassed view controller from another view controller in Swift

I have some problems to use subclasses in Swift, hope someone can help me.
What I have
Two view controllers:
VC1 with just some UIButtons
EffectVC that do some animation depending on the button pressed on VC1
import UIKit
protocol viewAnimation {
func initialStateSet()
func finalStateSet()
}
class EffectVC: UIViewController {
#IBOutlet weak var mainImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.initialStateSet()
}
override func viewDidAppear(animated: Bool) {
self.finalStateSet()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initialStateSet() {
}
func finalStateSet() {
}
}
class GrowingEffect : EffectVC, viewAnimation {
override func initialStateSet() {
// some stuff
}
override func finalStateSet() {
// other stuff
}
}
The problem
Maybe a simple question but I can't do what I want in Swift: I need to set a subclass according to the button that is pressed.
In other words I need to present subclassed view controller from my VC1 according to which button is pressed on VC1.
If I press the first button for example I want to show the VC 2 with the class GrowingEffect for use some custom stuff (this stuff must change according to the selected button).
What I tried
use IBAction for create my subclassed VC2 and show it
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController : UIViewController = storyboard.instantiateViewControllerWithIdentifier("EffectVC") as! GrowingEffect
self.presentViewController(destinationViewController, animated: true, completion: nil)
but I got
Could not cast value of type 'ViewAnimationDemo.EffectVC'
(0x109948570) to 'ViewAnimationDemo.GrowingEffect' (0x109948650).
use PrepareForSegue
but I can't set any subclass
What I really want to do
I know there are some other solution, like not using storyboard, but now I describe exactly what I want to do, hoping this is possibile:
have only one view controller in IB (EffectVC) associate with the class EffectVC. The class EffectVC has some subclasses like GrowingEffect.
In my code I want to instantiate the view controller EffectVC with the subclass that I need: for example instantiate the view controller in IB EffectVC with the class GrowingEffect.
I know that if I have one view controller for every subclass of EffectVC I can do what I want but I don't want so many view controller in IB because they are equal, the only things that I want to change are 2 methods.
I think there are some things mixed up in your setup. You should have 2 view controllers, each set up in its file, and each present in the storyboard with its identifier. It is ok if GrowingEffect inherits from EffectVC.
What you currently do with as! GrowingEffect is actually trying to cast the UIViewController instance you get from calling instantiateViewControllerWithIdentifier("EffectVC") to GrowingEffect. This will not work, because it is of type EffectVC.
Rather, you need to call instantiateViewControllerWithIdentifier("EffectVC") if button X is pressed, and instantiateViewControllerWithIdentifier("GrowingEffect") if button Y is pressed.
EDIT
If you use storyboard, you have to instantiate view controllers using instantiateViewControllerWithIdentifier. But you can only get an instance of GrowingEffect, if it is present on the storyboard.
It is not possible to "cast" an instance of EffectVC to GrowingEffect once created.
So, you have two possibilities here:
Use storyboard and put both view controllers on it. Use instantiateViewControllerWithIdentifier to instantiate the view controller you need, depending on the button pressed.
Do not use storyboard. Then you can create the needed view controller manually and use your UINavigationController's pushViewController method to present it.
You can't cast from parent class to child class, parent class just doesn't have the capacity to know what the child is doing. You can however cast from a child to parent, so you would want to set your view controller as GrowingEffect, then cast it to Effect, but again there is no strong suit to doing this either unless some method needs the parent class and you are using the child class. It looks like you need a redesign of how you want your view controllers laid out. Now I am assuming you have 2 children, lets call GrowingEffect and ShrinkingEffect. In your designer, you set your 1 to GrowingEffect and the other to ShrinkingEffect and make sure they have unique identifiers. Then you can use your view to present an Effect, and pass in either of those objects.

Swift: 'Tap to Begin' button - delete itself

I have a tap to begin button in my main view controller. It is linked to an IBAction that will detect when it is touched. However, I am wondering what would be the best way of hiding the button: deleted, moved off screen, or hidden completely - and what code should I put in my IBAction to do this?
Thanks
The easiest way is to just hide it. Create an outlet connection so you can reference the button as an class instance property, then set its hidden property to true from the tap handler:
#IBOutlet private weak var btnBegin: UIButton!
#IBAction func didTapBegin() {
...
btnBegin.hidden = true
}

Resources