I have a custom view created by subclassing UIView, and it has two UITextView subviews which are instantiated inside its init method. Let's say for clarifying things, that I have a superview A containing two UITextViews B and C.
On the other hand, I have the UIViewController VC that manages the superview A, and its responsible of its control. This means that it has the method that controls what happens when the user changes text on C.
When someone has to control a UITextView, he usually does something like
textView.delegate = self
I want to do the same, but C is not visible from VC. VC just uses A, so I have
a.delegate = self
and then I have come with the idea of doing this inside the implementation of A:
weak var delegate: UITextViewDelegate? {
get {
return c.delegate
}
set {
c.delegate = newValue
}
}
But this is giving me an error, saying that it's unwrapping an optional value whose actual value is nil.
So, what's the correct way of controlling UITextView C (that is inside UIView A) from VC, which only has an instance of A.
It is most probably that c maybe nil. To solve this, you need to set the delegate after c is initialised.
Alternatively, create your own delegate - ADelegate! This way you can name your own delegate methods with more meaningful names!
protocol ADelegate : class {
func cDidChange()
}
In A:
weak var delegate: ADelegate?
Now A should implement UITextViewDelegate, set c.delegate to self, and relay the methods to self.delegate.
If you have added the UITextField's in the Xcode Interface Builder, you can create outlets for the textfields in your ViewController (even when the textfields are sub-sub-children of the view, that the viewcontroller controls).
Thus continuing with your naming, you add the following outlets to your ViewController:
#IBOutlet weak var B: UITextField!
#IBOutlet weak var C: UITextField!
and in your ViewController's viewDidLoad() add:
B.delegate = self
C.delegate = self
If you created the UITextField's programmatically you can set their tag value to e.g. 1 and 2(0 is most likely already used) and then get the view from your ViewController like this:
let B = self.view.viewWithTag(1) as? UITextField
let C = self.view.viewWithTag(2) as? UITextField
Of course, make sure that the ViewController extends UITextFieldDelegate, that is:
class ViewController: UIViewController, UITextFieldDelegate {...}
Related
I am writing a custom table header view that can expand/collapse, I wrote a protocol for it like below:
protocol ExpandableHeadViewDelegate{
var expandStateReference : [String : Bool] { get set }
var tblVw : UITableView { get set }
func didTapActionFromHeadVw(_ view: ExpandableHeadView, tag: Int)
}
Here is my expandable head view class, what Im trying to achieve is when this view is tapped I will be able to call the methods that I need from the UITableView where expandableView is embedded:
class ExpandableHeadView: UIView {
var delegate : ExpandableHeadViewDelegate?
//set up tap recognizer
.
.
.
.
private func viewTapped {
if let delegate = delegate {
delegate.tblVw.reloadData()
}
}
}
My viewcontroller that utilizes this class is as below:
class PlayersViewController: UIViewController,UITableViewDelegate,
UITableViewDataSource, ExpandableHeadViewDelegate {
var expandedCells = [String : Bool]() //
#IBOutlet var tableVw: UITableView! // compile time error
}
The issue I am facing is even though I declare my 'var tableVw' in my delegate class, Xcode gives me an error:
Protocol requires property 'tableVw' with type 'UITableView'; do you
want to add stub?
Setting var expandedCells .. however sets properly. If I set my tableVw as non-IBOutlet it can compile, but I want my tableVw to be an IBOutlet property.
EDIT:
Adding a stub variable for tableVw and assigning my tableView IBOutlet to it works. But Im still curious if this can be achieved without using a stub variable
internal var tableVw: UITableView?
#IBOutlet var tableView: UITableView!
.
.
func viewDidload {
self.tableVw = self.tableView
}
Quite simply - declare the property in your protocol as UITableView!. The ! matters - a lot.
! makes a variable an optional, but an explicitly unwrapped one. This basically means, that the unwrapping is done for you, under the hood. You promise the compiler, that although this variable can contain a nil, you will make sure it will be set with a proper value before you try to access it. In case of IBOutlets this is done for you by the SDK.
I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}
I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().
I have an UIViewController
class WelcomeViewController: UIViewController
and an UIView
class SignUpView: UIView
Now I want to set in my WelcomeViewController delegate of SignUpView:
protocol SegueDelegate {
func runSegue(identifier: String)
}
class SignUpView: UIView { ... }
and connect it in
class WelcomeViewController: UIViewController, SegueDelegate {
how can I set in my WelcomeViiewController those delegate? When I'm trying to set:
override func viewDidLoad() {
SignUpView.delegate = self
}
it returns me
Instance member 'delegate' cannot be used on type 'SignUpView'
how can I find a solution?
You are trying to set delegate to a class. It should be an instance of the class i.e
let signUpView = SignUpView()
signUpView.delegate = self
What would be the point in doing that? If you want to navigate from one View to another, just add that Segue in Storyboard with an Identifier, so you can call self.performSegueWithIdentifier("IdentifierOfSegue", sender: self)
Create a weak property in SignUpView of that delegate(protocol) and name it other than delegate
then you can set and use it.
I agree with the developers saying "you can just do that via segue" but
the problem is you didn't declare a delegate var in the SignUpView class
so you can implement it in the signIn , if you declared it please write the line of code for me in a comment to check it
for now ...
I can suggest that you make a subview to be a parent class then override
which method you want to call
and you need to declare the delegate var as an optional (so you won't have
a memory cycle) like the following line ...
var delegate: SegueDelegate?
Let's solve this for people in need whom could need a solution when reading this issue:
In your UIView:
class SignUpView: UIView
you need to add:
var delegate : SegueDelegate?
Now, still in your class SignUpView, you need to add the function you want to delegate, just like this:
func runSegue(identifier: String) {
delegate?.runSegue(identifier)
}
This will call your delegate:
protocol SegueDelegate {
func runSegue(identifier: String)
}
Now, in your ViewController, you should have your SignUpView somewhere (created programmatically or linked through Storyboard / XIB).
In your viewDidLoadfunction, add: signUpView.delegate = self.
Don't forget to add SegueDelegatein your class heritage.
All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.