Goal: I'm building a time sheet using swift ui.
Question: At the top I have class A that inherits from UiViewController. It lays out a UiStackView in a vertical stack to divide the screen into three: top,center,bottom. I'm having issues inside the center view. I can only get interaction with the UiTextField inside the center if I add it directly.
Inside the center view, I have a class B that inherits from UiView. Within the class I have another UiStackView that will add a horizontal stack to layout some items nicely.
I've made a class C in the same file outside of the center view class, and it inherits from UiView. It contains a UiTextField that I setup and add.
Here's where things get weird, I can do inside class B, and I will be able to work with the UiTextField just fine:
`let temp = ClassC (inherits UiView)
self.addSubView(temp)` <- works
`ClassBUiStackView.addSubView(ClassC)
UiView temp = UiView(...)
temp.addSubView(ClassBUiStackView)
self.addSubView(temp)` <- fails
Let me provide some actual code:
`class A: UiViewController{
override func viewDidLoad(){
super.viewDidLoad()
self.top = ...
self.cnr = Class B
self.btm = ...
self.ClassAUiStackView(arrangedSubviews: [top,cnr,btm])
...constraints...
self.view.addSubview(ClassAUiStackView)
}
}
class B: UiView{
override init(...){
super.init(...)
self.ClassBUiStackView(...x,y,width,heigh...)
...constraints...
let temp = UiView(...)
self.ClassBUiStackView.addSubView(ClassC)
temp.addSubView(ClassBUiStackView)
}
}
class C: UiView{
var UiTF: UiTextField!
required init(..., classB, ...){
...setup UiTf...
self.UiTF.addTarget(self, action: #selector(uitfAction), for: .allEvents)
self.addSubView(UiTf)
}
#objc func uitfAction(senderL UITextField!){
...some action...
}
}
`
Any help on this would be much appreciated. This is my first Swift app so I'm still a newbie. Please elaborate as much, I will read it all (multiple-times). Thank you !
i found the answer that will make all my ui components responsive again, i had to add uiStackView.translatesAutoresizingMaskIntoConstraints = true, if this is taken away or set to false, the ui components do not respond.
Related
I am trying to apply styles on a custom class using UIAppearance()
class MainStyleButton: UIButton {}
with a code:
let buttonView = MainStyleButton.appearance()
buttonView.backgroundColor = Style.buttonColor
buttonView.layer.cornerRadius = 5
buttonView.layer.borderWidth = 5
buttonView.layer.borderColor = Style.buttonColor.cgColor
It works with color, but unfortunately doesn't make my button round. I would appreciate any tips.
Tested on simulator iPhone X, 8 with iOS 11.2.
I tried replicating your approach and set up a button. I tried to change the button's appearance in a UIViewController during viewDidLoad and also in the AppDelegate during applicationDidFinishLaunching using your code. I additionally tested changing the button type to .custom from the default type .system. None of this seemed to work, I could not override the same attributes that you couldn't.
From Apple's docs I understand that the button type defines its appearance and also which appearance attributes can be overridden:
A button’s type defines its basic appearance and behavior. You specify the type of a button at creation time using the init(type:) method or in your storyboard file. After creating a button, you cannot change its type.
I do not know why the attributes of interest to you are not changeable at this point
However I would like to offer a different approach that I personally use and allows you to change the buttons appearance. Since you already defined your custom class it is much simpler to define corner radius and other attributes that you would like, like so (or you could write a style function with parameters that you can call at any time, to be able to change the appearance based on where the button is used):
class MainStyleButton: UIButton {
override func awakeFromNib() {
layer.borderColor = Style.buttonColor.cgColor
layer.borderWidth = 5
layer.cornerRadius = 5
}
}
Or you can instantiate/use an IBOutlet for a system button and do this:
class MyViewController: UIViewController {
#IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// not necessary to do this is viewDidLoad, that's just my example
myButton.layer.borderColor = Style.buttonColor.cgColor
myButton.layer.cornerRadius = 5
myButton.layer.borderWidth = 5
}
I see this topic is discussed elsewhere but I don't see an answer to my questions.
I subclassed UIView to create a custom view. Currently I'm using interface builder to create a UIView and then setting the custom class option to my subclass.
First question. When I do that, how to I reference that instance from my code? I have a public function I would like to call that updates my view but I don't know how to call it from my view controller
Second question. I created an instance of the class from within my view controller just playing around and I noticed the public function I created isn't available with that instance. Can I create public functions when I inherit from UIView?
It is easy to do:
1)subclass UIView to create CustomView, add your public function,in your project:
import UIKit
class CunstomView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
public func printHello() {
print("hello")
}
}
2)In your storyboard, drag a UIView into your vc, and set the class to CunstomView, you can see that in my red frame:
3)click the Show the Assistant Editor, and ctrl-drag the view to the vc, set the name custom:
4)then in your vc's viewDidload function, you call the public function:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var custom: CunstomView!
override func viewDidLoad() {
super.viewDidLoad()
custom.printHello()
}
}
5)the result:
First question. When I do that, how to I reference that instance from
my code? I have a public function I would like to call that updates my
view but I don't know how to call it from my view controller
A: A view cannot exist by itself in app. You need viewcontroller to handle the custom view. Then in that VC, you can refer the view as IBOutlet.
Second question. I created an instance of the class from within my
view controller just playing around and I noticed the public function
I created isn't available with that instance. Can I create public
functions when I inherit from UIView?
A: You can create public function of custom view, just declare them in the header file. Then import the header in your VC. Then u can access it.
You can try to update your view from IB with the mothod below.
Objective-C:
-(void)awakeFromNib {
[super awakeFromNib];
//do something
}
Swift
override func awakeFromNib() {
super.awakeFromNib()
}
Second question
Do you mean the custom view don't answer the function you create within the view controller?
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())
I'm making a multiple choice quiz game, and my goal right now is to have four buttons that refresh by spinning around with new answer choices. I think that means I need a subview that animates and re-populates with new buttons--if that's incorrect or not best, please stop me here.
At any rate, I created the subview in my storyboard, put the buttons inside it (background is blue just to see it now):
I dragged that over to my ViewController to make an IBOutlet (buttonContainer) and added this code to my ViewDidLoad:
view.addSubview(buttonContainer)
let buttonTap = UITapGestureRecognizer(target:self, action: Selector("checkAnswer"))
buttonTap.numberOfTapsRequired = 1
buttonContainer.addGestureRecognizer(buttonTap)
buttonContainer.userInteractionEnabled = true
However: When I run it in the simulator, the blue background does not appear at all, but the buttons are still disabled.
Before creating the subview, both the buttons and the function (checkAnswer) they called all worked perfectly.
You don't need any of this code if you are creating everything in storyboard. Just create a new class for the containerview and connect the buttons as an outlet collection.
For example, your button container class might look something like this:
class ButtonContainerView: UIView {
#IBOutlet var answerButtons: [UIButton]!
func rotateButtons() {
for button in answerButtons {
var context = UIGraphicsGetCurrentContext()
UIView.beginAnimations(nil, context: &context)
UIView.setAnimationCurve(UIViewAnimationCurve.Linear)
UIView.setAnimationDuration(5.0)
button.transform = CGAffineTransformRotate(button.transform, CGFloat(M_PI))
UIView.commitAnimations()
}
}
}
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
}