i've create TabBar App with 5 tabs, all 5 views are the same and each view include buttons to control one set of equipment, switch input to output on receiver and control volume, so 5 views correspond to 5 rooms.
Now i have 5 different UIViewCOntrollers with nerly the same code, inherited from UIViewController.
But i think it should be one RoomViewController, inhereted from UIViewController, and RoomViewCOntroller shold be used for each room, something like this
class RoomViewController: UIViewController {
#IBOutlet weak var videoSat: UIButton!
func lockAudioLabel(sender: UIButton) {
if sender.isSelected {
return
}
videoSat.isSelected = false
}
#IBAction func videoSatSelect(_ sender: UIButton) {
//send command to equipment
//make some actions
lockVideoLabel(sender: sender)
}
}
And Room1ViewController is
class Room1ViewController: RoomViewController {
}
All rooms have scene in storyboard
So the question is there any way to use one set of iboulets and ibactions in RoomViewController class and inherit them for example in Room1ViewContoller? And how to access them?
So the question is there any way to use one set of iboulets and ibactions in RoomViewController class and inherit them for example in Room1ViewContoller?
The #IBOutlet properties and #IBAction methods you declare in RoomViewController are inherited by subclasses such as Room1ViewController.
But what is not inherited is the design in the nib. When you instantiate a subclass like Room1ViewController, it loads its own view, not some other view controller's view. This loading takes place on a per-instance basis. Therefore, you still need to hook up the interface objects in the nib to the properties and methods in the class declaration. In other words, you still need to make outlets and actions in the nib, because this is a different view.
Related
I have a storyboard, and it needs to be used in few places. Is there any other option than just duplicate storyboard and assign it to new view controller? I want to avoid boilerplate code and writing 3x same outlets and others. I need exactly same layout and almost all functions in all three controllers, but I want to only change model for all of them.
I have tried to do it like this:
class FirstViewController: UIViewController {
//outlets
// code
// tableView configuration
}
class SecondViewController: FirstViewController {
// and here I have just overrided some function to modify them
}
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
I am developing an app with 10 view controllers, including 2 webview controllers. Every view controller contains a 'send feedback' button on top right with exactly same style and functionality.
Right now, I am writing exact same code in each view controller for the buttons.
I was wondering if there is a way to write the method only at one place and use it for all the buttons, since the function is same.
I am using swift 3.
Create new subclass of view controller:
class SendFeedbackViewController: UIViewController {
#IBOutlet weak var sendFeedbackButton: UIButton!
#IBAction func sendFeedback() {
/* do whatever you need */
}
}
Then subclass all your view controllers from this new view controller:
class YourViewController: SendFeedbackViewController {
/* your entire logic */
}
In storyboard set class type:
Now you can connect your send feedback button outlet and action:
Use extension of UIViewController.
extension UIViewController {
func sendFeedBack() {
//write your code here
}
}
And call from any ViewController.
So I have a base class UIViewController called UITabBarTopLevelViewController:
class UITabBarTopLevelViewController: UIViewController {
#IBOutlet weak var uiNavItemTitle: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
// Do any additional setup after loading the view.
}
I then have two UIViewControllers that inherit from my base class and both look like this, except the second is called MyViewController2:
class MyViewController1: UITabBarTopLevelViewController {
//#IBOutlet weak var uiNavItemTitle: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
//uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
}
I add a Navigation Bar object to each child UIViewController super view and then I CTRL drag and add a new outlet to each UIViewController child class:
And here is the second CTRL drag outlet:
These are different references, but I can comment out the #IBOutlet weak var uiNavItemTitle: UINavigationItem! in my child classes and reference one time only in the base class UITabBarTopLevelViewController, for both MyViewController1 and MyViewController2.
You can see, when I hover over the outlet circle, in my base class, it highlights both UINavigationItem in the Story Board:
I am surprised this works, its nice for me that it works because I only need to set the uiNavItemTitle.titleView for my logo one time for both views. This is what I want but there seems to be a bit of magic that I can reference multiple outlet references one time in my base class and there is no bugs or crashes.
I currently have no memory leaks or crashes and my app is working just fine and doing exactly as I desire.
Will there be any bugs with this?
Could someone explain how this is working?
Is the fact that this works, not surprising to experienced Swift developers?
That's how subclass exactly works.
You placed a UINavigationItem in the base class UITabBarTopLevelViewController through
uiNavItemTitle.titleView = CoreUtility.LogoForUINavBarGet()
Also, MyViewController1 and MyViewController2 inherit from the base class UITabBarTopLevelViewController. That's say these child viewControllers both have a UINavigationItem which inherit from their UITabBarTopLevelViewController.
This is not a bug, on the other hand, more like a topic about design pattern though. You could place all the base stuff into a base class, inherit from those classes and implement the specific detail within the child class.
HTH.
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.