How to add a view to a super class view controller? - ios

I have a UIViewController Car that is a subclass of another UIViewController Vehicle.
class Vehicle: UIViewController {
override func viewDidLoad() {
let vehicleLabel = UILabel()
vehicleLabel.text = "This is the Vehicle"
view.addSubview(vehicleLabel)
}
}
class Car: Vehicle {
override func viewDidLoad() {
super.viewDidLoad()
let carLabel = UILabel()
carLabel.text = "This is the Car"
super.view.addSubview(carLabel)
}
}
// called like this
let carController = Car()
presentViewController(carController, animated: false, completion: nil)
From Car I would like to add a UILabel to the Vehicle top level view. However all my attempts at this end up adding it to the Car view instead.
I've tried directly accessing the parent view:
super.view.addSubview(carLabel)
also tried creating a function to add views on Vehicle:
func addToSuperView(label: UILabel){
self.view.addSubview(label)
}
also tried playing with superclass but failed. I end up with this, where the labels are on the view associated with the view of the UIViewController where they were created. I'd like both labels on Vehicle.
How do I do this?

Edit
The exploded view isn't what you should be looking at, because the most frequently used UIKit classes have a number of wrapper views and container views that are used by the classes privately.
What you can be certain of is that no instance of Vehicle or its view can exist, because you haven't coded for it. Notice also the view hierarchy - there is only one label, not two.

A "super.view" is NOT IDENTICAL with your parent UIViewController's view.
A "super.view" is something in a view hierarchy.
A parent or child is something in a class inheritance.
Both things are quite different!
remark: UpperCaseWriting = ONLY for marking purposes (to accentuate key arguments)

#Fook, For Car to add a UILabel to its superview, in this case Vehicle, Car must be a subclass of UIView (not UIViewController or Vehicle) and must be added as a subview to Vehicle's view. Then Car can add a UILabel to its superview with:
Car.superview?.addSubview(UILabel())
If Car must inherit from UIViewController or Vehicle then Car must be presented by Vehicle. It doesn't matter whether Car inherits from Vehicle or not. Then Car can add a subview to its the view controller that presents it by saying:
presentingViewController?.view.addSubview(UILabel())

Related

Is a blank function conventional in subclass that conforms to custom protocol?

I have two main screens in my app, currently both just subclasses of UIViewController. These two view controllers are very similar - they both implement my custom subclass of UIView called HeaderView that is responsible for displaying information and taking user input. As it stands, this code is repetitive because the HeaderView setup is the same for both view controllers - the only difference is what happens when the user confirms the text entry in HeaderView.
To cut down on repetitive code, I am creating a class called InputViewController (a subclass of UIViewController) that houses the aspects of the two view controllers that are identical. Eventually, I want the two view controllers to subclass InputViewController instead of UIViewController.
class InputViewController: UIViewController, InputProtocol {
private let headerView = HeaderView()
override func viewDidLoad() {
super.viewDidLoad()
// layout, etc.
setupCallbacks()
}
internal func setupCallbacks() {
headerView.onUpdate = { (text: String) in
// called when user confirms text entry in headerView
self.onHeaderUpdate()
}
}
internal func onHeaderUpdate() {} // Blank function
}
setupCallbacks() and onHeaderUpdate() are methods defined in the protocol that the InputViewController conforms to. The HeaderView implements a callback closure that is handled in setupCallbacks() by headerView.onUpdate...
The protocol that InputViewController conforms to:
protocol InputProtocol {
func setupCallbacks()
func onHeaderUpdate()
}
To illustrate this, I drew up a diagram;
Since I want the subclasses of InputViewController to override the onHeaderUpdate() method, is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank or is there another solution to this?
is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank
Yes, that is called an abstract method. It is common to give it code that crashes deliberately, as a way of saying, “I exist only to be overridden in a subclass.”
(I should go further and say that what you are creating, a base view controller that carries out initial configurations that all subclasses must implement, is also normal.)

UITapGestureRecognizer not firing

Let's get the google results out of the way
.userInteractionEnabled IS true
The view IS hit (using a symbolic breakpoint with -[UIWindow sendEvent:] and po $arg3)
Now on to how I have this structured, which is an attempt to make models totally removed from view code.
The gist is that I have these classes:
class CarModel - pure data
class CarModelDisplayClass - a class that carries a Model, and can conform to Displayable and Tappable. This is the class that the later BuilderClass will deal with, it basically acts as a bridge between the Models and the Views.
protocol Displayable - To make a class return a view for the later BuilderClass to attach to a view/screen
protocol Tappable - The BuilderClass looks at conformance to attach a tap gesture to the view (which is returned from the Displayable protocol))
The Builder works like this:
Hardcode-build a bunch of CarModels
Hardcode-build a bunch of CarModelDisplayClass with the models
Send the list of CarModelDisplayClass into a method that translates the list into actual views and gesture recognizers (by looking at protocol conformance)
Attach these views to an actual UIViewController
Present the UIViewController
At this point it all works, except the UITapGestureRecognizer.
The CarModelDisplayClass to actual views+gestures looks like this.
for item in items {
let view = item.view() // Get the view from the Displayable protocol
superView.addSubview(view)
if let i = item as? Tappable { // Check Tappable conformance
let gesture = UITapGestureRecognizer(target: i, action: #selector(i.tapped))
view.addGestureRecognizer(gesture)
view.isUserInteractionEnabled = true
}
}
I am not sure if there is something obvious I am missing. I thought maybe there is something related to the target i is the issue, however I have tried directing it to item as well (if that would even matter I don't know).
Any pointers would help.
I have the real code here (it has different names though)
The Displayable and Tappable protocols
Pure model class
Example of a display class that takes a model and conforms to display to return a simple label, and conforms to tappable with a method
The view builder that converts display classes into actual views and adds gesture recognizers to them if needed
The high level builder that collects models, display classes and presents a VC
It seems that you will have to keep a strong reference to your Tappable items somewhere in code otherwise they will be removed from memory.
Based on the code in attached links - I would change TLAStackviewBuilder class to return UIScrollView, but with references to displayRows in it.
In code that you wrote above:
class Something {
let storedItems: [Any]!
func someFunc(items: Tappable) {
storedItems = items
for item in items {
let view = item.view() // Get the view from the Displayable protocol
superView.addSubview(view)
if let i = item as? Tappable { // Check Tappable conformance
let gesture = UITapGestureRecognizer(target: i, action: #selector(i.tapped))
view.addGestureRecognizer(gesture)
view.isUserInteractionEnabled = true
}
}
}
}

Inherit outlets and actions in view controller

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.

UIView subclass access ViewController methods swift

In a couple of my projects I think I'm not created a great structure in many cases.
It could be a game where I've created a game board (think about chess) with a grid of 8 * 8 cells. Each cell has a gesture recognizer that relies on a subclass (cell.swift), with the game logic in a parent ViewController.
For arguments sake, let us say we want to display to the user which square they have touched.
I've found out how to do this from the subclassed UIView (obvs. create the alert in the subclassed UIView / cell.swift in this example)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
but it seems to break the structure of the app - but wouldn't it be the same accessing an action in the parent ViewController? What is the best way of approaching this>
Your rootViewController is the VC on the bottom of your stack. It's not a safe way to access the visible VC, and is rarely useful, in general (there are cases, but I doubt your app would find them useful).
What you likely want to use is a delegate pattern. Let's say the parent VC that displays your chess board (let's call this MyBoardViewController), conforms to a protocol like the following. MyView is whatever custom UIView class you're using for the chess squares:
protocol SquareAlertHandler {
func handleSquarePressed(sender : myView)
}
And add the following property to your MyView class:
weak var delegate : SquareAlertHandler?
And replace whatever event handler you're currently using, with the following (I'm assuming you're using a UIButton in IB to handle the press, and have arbitrarily named the outlet 'didPress:'):
#IBAction didPress(sender : UIButton) {
delegate?.handleSquarePressed(self)
}
Now, add the protocol to your MyBoardViewController, and define the method:
class MyBoardViewController : UIViewController, SquareAlertHandler {
... ... ...
func handleSquarePressed(sender : myView) {
// Do something to handle the press, here, like alert the user
}
... ... ...
}
And finally, wherever you create the MyView instances, assign the MyBoardViewController instance as the delegate, and you're good to go.
Depending on your Swift literacy, this may be confusing. Adding code, so that I can at least match up the class names, would help to clarify things.

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.

Resources