Singleton computed property in swift 3 - ios

I am trying to achieve a singleton UIView instance in my code, so whenever I create an instance of 'MyView' the nib file will load for only once and then reuse it again:
class MyView : UIView {
#IBOutlet weak var someLabel: UILabel!
static var customeView : UIView = {
let view = Bundle.main.loadNibNamed(String(describing: MyView.self), owner: self, options: nil)?[0] as! UIView
return view
}()
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 576, height: 30))
let viewForOnce = MyView.customeView
viewForOnce.frame = bounds
}
}
The problem is that whenever I call the MyView.customeView it get crashed saying that 'this class is not key value coding-compliant', I think this is happening because of the 'owner: self' inside the computed property.
Any help will be appreciated.

The issue is most probably with your ReceiptView.xib. You should review it first.
Look for an IBOutlet, which is there in a xib, but not in your ReceiptView class and delete it.
Edit:
I meant with the singleton is loading the nib only once whenever I
create and instance of that class.
No, this is not going to work like that. Every time init is called, your computed property is also called and a new instance is created from xib every time.
As Carien van Zyl already mentioned, you are using self in a class var which corresponds to MyView class itself (or it's subclass if its called for a subclass), not an instance. Try passing nil as owner instead.
The whole technique is looking wrong to me. You should not use singleton pattern with UIView subclasses.
There is nothing wrong in calling loadNibNamed multiple times and create exactly the same instances. If you want to use the same instance multiple times in a view hierarchy, it's not possible since every view can have only one superview. In this case you should follow MVC pattern: create multiple MyView instances -> update model whenever you change something in a view and want those changes to be reflected elsewhere -> update another view using updated model.

customeView is a type property. Therefor, self inside of it, will reference MyView.self which is a class type. The class type do not hold the instance variables, which includes someLabel.
See Apple's documentation on Types

Related

Singleton variable not updating

The value of the variable 'switcheroo' in the view controller below is always the same when I attempt to access it via a singleton. I am trying to access its value from a custom label class that prints the characters of the label one by one. When the label is set, I try to get the updated value of switcheroo in the Viewcontroller singleton. However it always returns the initial value of switcheroo, not the updated value (which I can trace in the viewcontroller). Am I doing something wrong?
class TheViewController: UITableViewController, UIGestureRecognizerDelegate, UITabBarControllerDelegate {
static let shared = TheViewController()
var switcheroo = 0
... various operations that change the value of switcheroo...
}
class CustomLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var attributedText: NSAttributedString? {
didSet {
DispatchQueue.main.async {
let characterDelay = TimeInterval(0.01 + Float(arc4random()) / Float(UInt32.max)) / 100
for (index, _) in attributedText.string.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
print("switcheroo value in TheViewController is now: \(TheViewController.shared.switcheroo)")
super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
}
}
}
}
I would not suggest making a view controller a singleton solely for the purpose of some shared state or model property. View controllers have their own life cycle patterns (e.g. instantiated from storyboard scenes, released when they are dismissed, recreated when presented again, etc.), and you’re likely to have issues arising from dealing with separate instances of your view controller(s).
Instead, don’t try to fight the standard view controller life cycle, but rather just move this property into a separate object, e.g.
final class StateManager {
static let shared = StateManager()
var switcheroo = 0
private init() { }
}
Then your view controllers can use that:
class ViewController: UIViewController {
...
func examineSwitcheroo() {
print(StateManager.shared.switcheroo)
}
func updateSwitcheroo(to value: Int) {
StateManager.shared.switcheroo = value
}
}
This way, you enjoy this shared state, without entangling normal view controller life cycles in this process.
Now, what the right name for this singleton, StateManager in my example, depends entirely upon what this shared property is. But there’s not enough information about what this switcheroo object really is to offer better counsel on this matter.
And, probably needless to say, it’s a separate question as to whether you really should be using singletons at all for state variables and model objects, but that’s beyond the scope of this question.
If you have determined that having a ViewController singleton is the right decision, the likely answer is that you are not using that shared instance every time, instead accidentally calling the initializer at some point in your project (possibly Xcode is doing it automatically through interfaces).
To search through your entire project, you can use cmd + shift + F and then type TheViewController(). There should only be one occurrence (the shared instance). Be sure to also check for TheViewController.init(). That will find any time you do it.
If the issue persists, perhaps try setting the shared instance to self in the viewDidLoad method of TheViewController?
Hope this helps!
Don't manage your application's data in your view controller(s). The Cocoa and Cocoa Touch frameworks use the MVC paradigm, where the M is meant to stand for model, i.e. the application's data model. Any data that needs to be preserved, or that's relevant beyond the scope of the view controller, should be stored and managed in a model object. If you give your view controller's a reference to the model when you create them, you never need to worry about passing data from one view controller to another; instead, they each operate on the model, and any data they need comes from the model.

Enabling two-way communication between two classes

So I have a custom view touchableView inside a ViewController.
touchableView informs ViewController of changes in its properties through a delegate protocol (ViewController being the delegate).
What is the best method to change properties of touchableView from ViewController (so the other way around)?
Is there a way to create a two-way delegate relationship between two classes?
Simply:
Assuming that you are already have touchableView instance in the ViewController, you should be able to set -or get-/call its properties and methods.
For instance, assume that you have the following method in touchableView class:
class func fromNib() -> TouchableView {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)![0] as! TouchableView
}
You should simply be able to:
let touchableView = TouchableView.fromNib()
thus:
// for example
touchableView.myProperty = "Hello"
I assumed that TouchableView has a string property called myProperty...
Remark:
For some reason, I suggest to implement some of property observers in TouchableView:
Property observers observe and respond to changes in a property’s
value. Property observers are called every time a property’s value is
set, even if the new value is the same as the property’s current
value.
They might be -somehow- useful to be implemented in your custom class. For clarity, let's consider -for instance- that if editing the value of myProperty should be effecting the background color of the view, it might be implemented as:
var myProperty: String = "Initial Value" {
willSet {
print("About to set value to: \(newValue)")
}
didSet {
backgroundColor = UIColor.red
}
}
Further Reading:
If you are looking for an advanced approach for two way bindings (Implementing MVVM), you might want to check this article.
I would also suggest to take a look at some frameworks that will be so helpful for such an approach, such as RxSwift, for a more simple framework, you might want to check ReactiveKit/Bond.

How to use foreign variables in classes

I have two .swift files so I want one of them to modify the text of an IBoutlet label that is the other class.
Class 1:
class class1: UIViewController {
#IBOutlet var label1: UILabel!
}
Class 2:
class class2: SKScene {
var cool_variable = 1
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
}
by doing this I'm getting the following error message:
Instance member "label1" cannot be used on type "class2"
Thanks in advance!
The distinction and relationship between classes and instances is absolutely crucial to object-oriented programming. Thus, consider your proposed code:
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
The problem here is that class1 is the name of a class. (That would be more obvious if you just, please, follow the elementary rule that class names start with a capital letter.) But label1 is an instance property. Thus what you need here is the name of an instance of the class1 class — presumably a reference to the actual existing instance that is already part of the view hierarchy.
You never create an instance of class1 in class2 to access the variables.
My guess is that you are using Storyboards. In this case you wouldn't want to create an instance of class1. Instead you would use delegation (This would also be a good idea if you are not using Storyboards).
Delegation can be a complicated topic, so I will try to keep this simple.
First, you start with a protocol. Usually you name it something like <CLASS-NAME>DataSource, so you would do something like:
protocol class2DataSource: class {}
The class keyword is required for delegation protocols.
Then you would add the methods to it that you want called in other classes when you call a method in the class the protocol delegates for, so, for example, willCoolFunc:
protocol class2DataSource: class {
func willCoolFunc(with variable: Int)
}
You have the parameter so you can access the variable cool_variable as you are trying to.
Next, you need to create a a variable in class2 that is of type class2DataSource:
weak var dataSource: class2DataSource?
Make sure the variable is weak and an optional.
Next, call the method, you would do it in coolFunc:
func coolFunc () {
dataSource?.willCoolFunc(with: cool_variable)
}
Now you, to access cool_variable when the function is called, you need to implement class2DataSource on class1. Create an extension of class1 that implements class2DataSource and add the function willCoolFunc:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
}
}
Now you can access the variable cool_variable in class1! The reason why is because when you call class2.coolFunc(), the willCoolFunc method is called with cool_variable passed in. Any class that implements the class2DataSource can access cool_variable with the willCoolFunc method.
To finish of the method, here is what the extension would look like:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
self.label1.text = "\(variable)"
}
}
We are almost done, but not quite. We still have to set the class1 as the data source for class2DataSource. To do this, I will reference Nikolay Mamaev from this post:
Go to the Interface Builder.
Type "Object" in the search text field of the Objects Library and drag an 'Object' to your view controller containing that connects to
class1 (i.e. do the same as you add any view or view controller to
storyboard scene, with the exception you add not a view or view
controller but an abstract object).
In the left-side 'Scenes' panel of your storyboard, highlight just added Object; in right-side panel go to the 'Identity Inspector' and
type class2DataSource instead of pre-defined NSObject. In left side
panel ('Scenes'), 'Object' will be renamed to 'Class2 Data Source'
automatically.
In the left-side 'Scenes' panel of your storyboard, control-drag from your UIView [*not the controller*] to the 'Class2 Data Source';
in pop-up appeared, select dataSource outlet.
There! You now set class1's label1's text to the value of cool_variable when you call class2.coolFunc()!
I do not know the exact problem you're trying to solve, but I'm just going to consider the part that you want to access the variable in class1 in class2. There are two basic ways to go about this, one is Inheritance and the other one is by creating an object. These are basic Object Oriented Programming concepts which you need to be familiar with.
Swift does not support multiple inheritance. So that rules out inheritance for solving you problem, since you are inheriting two classes SKScene and UIViewController.
Create an object in the class1 and call the function coolFunc
class class1: UIViewController {
#IBOutlet var label1: UILabel!
func modifyLabel(){
let someObject = class2()
someObject.coolFunc()
}
}
Of course this solution might not be the one you're looking for. You will have to explain more about the problem you're facing if you need a solution that will work for you.

How to set up cross-referencing between UIViews in Swift

First q here, so trying to get protocol right...what I've done works, in that the data and views display correctly, but memory is not deallocating (still getting used to ARC after many years of allocating/deallocating), and I'm trying to figure out the right strategy. Document based app. When doc is created, view controller is instantiated, which creates several views which need to refer to each other for size/position/methods, and all of which need access to the doc data.
class MyDoc: UIDocument {
var data: Int
etc...
}
class MyController: UIViewController {
var doc: myDoc! // code which creates MyDoc instance assigns self to this property
var thisView1: MyView1!
var thisView2: MyView2!
thisView1 = MyView1(...)
thisView2 = MyView2(...)
thisView1.theOtherView2 = thisView2
thisView2.theOtherView1 = thisView1
thisView1.doc = self.doc
thisView2.doc = self.doc
}
class MyView1: UIView {
var theOtherView2: MyView2!
var doc: MyDoc!
}
class MyView2: UIView {
var theOtherView1: MyView1!
var doc: MyDoc!
}
I don't think I excluded anything meaningful. Assigning thisView1 and thisView2 to each other creates a strong reference cycle, right? I tried combining an unowned property on one with implicitly unwrapped on the other, per The Swift Programming Language, but kept having trouble with the init() methods of the views. Is using weak references and then unwrapping the optional all the time (though I make sure there's a valid value before proceeding from viewController) the only thing that'll work? I've read so many explanations that each new one now confuses me more.
Thank you!

Accessing an IBOutlet from another class

I am new to Swift/iOS, so please bear with me:
I am trying to access a function in one class from another class, and update an UIImage name.
Within my viewcontroller class I have
class Documents: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
override func viewDidLoad() {
super.viewDidLoad()
UpdateImage()
}
func UpdateImage() {
UpdateImage.image = UIImage(named: "NewImage")
}
}
Everything works, the Image gets updated to "NewImage"
Question: I can access the UpdateImage func from another class, but why is it generating an error when trying to change the image in the Documents class?
class GetChanges {
var success = { operation:AFHTTPRequestOperation!, response:AnyObject!) -> Void in
var MakeChange = Documents()
MakeChange.UpdateImage()
}
}
This generates an error on the "UpdateImage.image = UIImage(named: "NewImage")" in the Documents Class; "fatal error: unexpectedly found nil while unwrapping an Optional value"
When you call it within the class itself, it is operating on itself and it has already been created from a nib/storyboard. This means that UpdateImage exists.
When you call the method from another class, when you call this line:
var MakeChange = Documents()
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage. Because this value doesn't exist, it unexpectedly finds nil and throws an error.
You need to somehow retain a reference to the instance of Documents you're trying to display. I'd need more information to tell you how to do that.
Also, because you mentioned that you're new, I'd like to point out a few issues I notice with your code that is making it very difficult to read.
Capitalized names are reserved for Types variable names should (almost) never begin with a capital letter.
Variable names should reflect the object they represent. UpdateImage sounds like it is an image. It would be better to name this updateImageView
Functions should be lowercase as well. It is strange to see capitalization this way and makes the code a bit uncomfortable to read.
Good luck!
Read about View Contoller's lifecycle, it's very important knowledge for iOS developer.
As Logan said:
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage
This means that after call init for ViewController (i.e. Documents()) nib isn't loaded. You can use outlets of viewController in another code only after viewDidLoad stage. Apple docs:
The nib file you specify is not loaded right away. It is loaded the first time the view controller's view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad() method and perform your tasks there.
You can remove MakeChange.UpdateImage(), because it will be called in viewDidLoad. Or, if you want pass specific image name to view controller:
class Documents: UIViewController, UITableViewDataSource,
UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
updateImageView()
}
func updateImageView() {
if let imageName = imageName {
UpdateImage.image = UIImage(named: imageName)
}
}
}
After that, you can use
let documentsViewController = Documents
documentsViewController.imageName = "newImage"
When you load documentsViewController, newImage will be presented

Resources