Swift Reference Variable from Specific Instance of Different Class - ios

I'm new to Swift and iOS in general. I'm using Swift to write an app. This app has two files, ViewController.swift and BTService.swift.
ViewController.swift has a class ViewController of type UIViewController, and BTService.swift has a class BTService of types NSObject and CBPeripheralDelegate. I have a slider set up in the UIViewController class, and its value is assigned to variable currentValue.
Now, I want to be able to reference currentValue from within the BTService class. How can I go about doing this? I've noticed that if I define a variable, test, in the file ViewController before the class UIViewController, that I can reference test in BTService. But that's of no use to me since I can't (to my knowledge) get the slider value to be assigned to test unless test is defined within the UIViewController class.
Here's my ViewController and the currentValue variable definition.
class ViewController: UIViewController {
#IBOutlet var positionLabel: UILabel!
#IBOutlet var positionSlider: UISlider!
#IBOutlet var connectionLabel: UILabel!
#IBAction func sliderValChanged(sender: UISlider) {
var currentValue = Float(positionSlider.value)
Any help would be greatly appreciated! Thanks.

You can use NSUserDefaults to store data within your application and share it between view controllers.
In the example you give, you could store it like this:
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setFloat(Float(positionSlider.value), forKey: "sliderValue") // Write to NSUserDefaults
defaults.synchronize()
Then, whenever you need access to it in another file (or in this one), you can just call floatForKey:
if let currentValue: Float = defaults.floatForKey("sliderValue") {
// Access slider value
} else {
// Probably not saved
}

Is your BTService a property within the UIViewController?
Could you use Key Value Observing (KVO)
Inside your UIViewController
override func viewDidLoad() {
self.addObserver(self, forKeyPath: "positionSlider.value", options: .New, context: nil)
}
deinit() {
self.removeObserver(self, forKeyPath: "positionSlider.value")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if keyPath == "positionSlider.value" {
// update service property
}
}

Related

Convenience init for UIViewController not appearing when added via extension

I'm wanting to add a convenience initilizer to UIViewController via an extension because I want all UIViewControllers/UIViewController subclasses to have access to it. But when I add it, it doesn't appear in the drop down list of available initilizers and if I try to use it I get an error saying Missing argument label 'coder:' in call.
extension UIViewController {
convenience init(test: String) {
self.init(nibName: nil, bundle: nil)
print(test)
}
let testController = TestController(test: "Hello World!") // Missing argument label 'coder:' in call
Is there some kind of trick to get this to work?
I am able to add convenience initilizer's to other UIKit classes and have them appear as available inits.
Its working fine as you can check and match your code, maybe you need to delete derived data:
extension UIViewController {
convenience init(test: String) {
self.init(nibName: nil, bundle: nil)
print(test)
}
}
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textf: UITextField!
var doubleValue: Double?
override func viewDidLoad() {
super.viewDidLoad()
let testController = SecondViewController(test: "ffwfew")
print(testController)
}
}

How to move data in VC using Delegates?

How can I move the data stored in data into the next VC and append it in my list when the sendDate is tapped ? Here is my code of the sending class :
protocol DataSentDelegate{
func userDidEnterData(data: String)
}
class SecondViewController: UIViewController{
var delegate: DataSentDelegate!
#IBAction func addItem(_ sender: Any)
{
let data = textField.text
delegate?.userDidEnterData(data: data)
}
Here is the code of recieving class :
class SecondPageViewController: UIViewController, DataSentDelegate{
func userDidEnterData(data: String) {
}
#IBAction func sendDate(_ sender: UIDatePicker) {
}
How do I implement list.append(data!) where data holds the value of textField.text
Do you not have to set the delegate in SecondPageViewController to self (normally in ViewDidLoad?
You declared it but don't seem to have assigned it.
You can share data through whole project using Singleton Pattern.
A singleton class is initialised for only once.
Look at these answers:
Swift - set delegate for singleton
What about you just add an array variable in your SecondPageViewController which will hold a list of strings, then append a new string each time sendDate delegate method gets called?
A few other remarks, there is no need to declare your delegate var as implicitly unwrapped if you're using optional chaining anyway, just declare it as an optional. Secondly, because SecondPageViewController is a class, it's better to make your delegate protocol class bound as such: protocol DataSentDelegate: class { func userDidEnterData(data: String) }, thirdly to avoid possible strong reference cycles make your delegate var a weak one.

swift: defer non-optional object initialization

When dialing with CocoaTouch, it often happens that UIView(Controller) subclass properties can't be initialized in init method (ex. we need view already loaded), but logically they are non-optional and even non-var. In such cases the property must be optional to compile without errors, what looks pretty ugly - the code is fulfilled with !.
Is there any way to solve this problem? I would imagine some deferred initialization. Perfectly if such property can compile without initial value and crash at runtime if it's accessed prior to be initialized.
Some code sample to describe the issue:
class MyVC: UIViewController {
#IBOutlet var someLabel: UILabel!
let viewBasedParam: CustomClass // how to keep it non-optional if it can be initialized after view has been loaded?
override func viewDidLoad() {
super.viewDidLoad()
self.viewBasedParam = CustomClass(self.someLabel.text)
}
}
P.S. CustomClass can't have default initializer, because it requires data from view.
In your MyVC you can have a convenience init method where you can initialize the let variables. Try this and let me know if it works for you:
class MyVC: UIViewController {
let viewBasedParam: CustomClass
convenience init() {
self.init(nibName:nil, bundle:nil)
self.viewBasedParam = CustomClass(self.someLabel.text)//else just initialize with empty string here and then assign actual value in viewDidLoad
}
}
As far as I've discovered the workaround solution may be following:
class MyVC: UIViewController {
#IBOutlet var someLabel: UILabel!
private var _viewBasedParam: CustomClass? = nil
var viewBasedParam: CustomClass {
get { return self._viewBasedParam! } // always unwrap private optional
set { self._viewBasedParam = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
self.viewBasedParam = CustomClass(self.someLabel.text)
}
}

Observe property change of class instance

Given an instance of a class, how can I observe a property change?
For e.g., I'm building an SDK that initializes a host app's chat view to provide more functionality with a simple inplementation that looks like:
sdk.initialize(chatView)
In that initializing function, I need to track the host app's chat-view's hidden property so that the SDK's view matches.
A simple KVO example for observing hidden:
class SDKViewController : UIViewController {
private var context = 0
private var observingView: UIView?
func initialize(view: UIView) {
removeObservations()
observingView = view
// start observing changes to hidden property of UIView
observingView?.addObserver(self, forKeyPath: "hidden", options: [.New], context: &context)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let newValue = change?[NSKeyValueChangeNewKey] as? Bool where context == &self.context {
print("hidden changed: \(newValue)")
}
}
// this is called by deinit
// it should also be called if they can deregister the view from your SDK
func removeObservations() {
if let view = observingView {
view.removeObserver(self, forKeyPath: "hidden")
observingView = nil
}
}
deinit {
removeObservations()
}
}
This is making some assumptions about your configuration, but if you allow initialization of many views, you can adjust easily.
Also, a lot of this is more concise if you use KVOController by Facebook, which is not in Swift.
Edit: Just to note, hidden does work with KVO.
Edit #2: Updated YourSDKClass to SDKViewController (NSObject -> UIViewController)
Here is an example using protocols
protocol MyClassDelegate:class {
func myClassValueDidChange(newValue:Int)
}
class MyClass {
weak var delegate:MyClassDelegate?
var value = 0 {
didSet {
delegate?.myClassValueDidChange(value)
}
}
}
class ViewController:UIViewController,MyClassDelegate {
let myClass = MyClass()
override func viewDidLoad() {
super.viewDidLoad()
myClass.delegate = self
}
func myClassValueDidChange(newValue:Int) {
//Do something
}
}
You can use Key Value Observing (KVO) to monitor changes to general properties on classes, which includes the hidden property on UIView instances. This is done using addObserver:forKeyPath:options:context: defined in the NSKeyValueObserving protocol.
Note that you can also hide a view by removing it from its superview or by setting its alpha to zero.

How to make data visible for all view controllers?

Let's consider the following case:
I have a tab bar application where tapping each tab bar item takes user to other view that is handled by different view controller(typical pattern).
In one of my controllers I have method that downloads the important data and I want them to be global for whole application. What design pattern should I use?
One way to do that is to store this data using persistence such as core data, but is it the only way to make data visible for all view controllers? Maybe app delegate is able to perform such actions?
How in general you solve such situation where you have some data or variable which should be visible for all view controllers in your project?
Note that I'm not asking about persisting data across launches of the app, I just wonder how to make some data global in terms of the whole project.
Dont (emphasize DON'T) use following:
Singletons
AppDelegate (just another Singleton)
NSUserDefaults
Rather Don't:
Core Data
Do:
pass in either during instantiation or via properties
Why?
The DON'Ts messes up your memory
the Rather Don't messes with several principals of SOLID.
How would you do it correctly:
Create a base view controller that has a property that takes your data, make all your view controller inherit from it.
subclass UITabBarController
if a new view controller is selected, set the data to the view controller
the implementation is a bit tricky, this is from a real world app
class ContentTabBarController : UITabBarController {
private var kvoSelectedViewControllerContext: UInt8 = 1
required init(coder aDecoder: NSCoder) {
self.addObserver(self, forKeyPath: "selectedViewController", options: .New | .Old | .Initial , context: &kvoSelectedViewControllerContext)
}
deinit{
self.removeObserver(self, forKeyPath: "selectedViewController")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if context == &kvoSelectedViewControllerContext {
var targetVC : UIViewController?
if let viewController = change["new"] as? UIViewController{
if let oldViewController = change["old"] as? UIViewController{
if viewController != oldViewController {
targetVC = viewController
}
}
} else {
targetVC = self.viewControllers![0] as? UIViewController
}
self.configureTargetViewController(targetVC)
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.translucent = false
}
func configureTargetViewController(viewController: UIViewController?){
//setup data
}
}
How does the tab bar controller get the data.
Well, that is up to you. It could fetch core data, it could actually pass a fetching manager as data. It could read from disc or network. But for a sane design it should not do it itself but use another class for it. and an instance of this fetcher class should be set from outside the tab bar controller, i.e. from the App Delegate.
One easy way would be to make a struct and make it hold variables. Then, you can edit it anytime you would want to. For example:
struct Variables {
static var number = 4
}
Then you can edit the data inside Variables in any view controller you want by doing this code.
Variables.number = 6 //or any other number you want
A cleaner and efficient, although not necessarily different, way to do this is to create a singleton class, e.g. AppData, which you can access in a variety of ways, and which would be available to all your other classes. It has the benefit of separating your app-specific stuff from the app delegate stuff. You might define the class this way:
#interface AppData : NSObject
// Perhaps you'll declare some class methods here & objects...
#end
you can define ivars for the AppData class, and then manage a singleton instance of AppData. Use a class method, e.g. +sharedInstance, to get a handle to the singleton on which you could then call mehods. For example,
[[AppData sharedInstance] someMethod:myArgument];
Your implementation of +sharedInstance can be where you manage the actual creation of the singleton, which the method ultimately returns.
Try this simple method,
1) Create a variable in appdelegate.swift that could be visible to all viewcontroller.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
...
var Check:String!="" // to pass string
...
...
}
2) Create appdelegate instance in any viewcontroller
viewcontroller1.swift
import UIKit
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
...
...
override func viewDidLoad() {
super.viewDidLoad()
...
var tmp = "\(appDelegate.Check)"
appDelegate.Check="Modified"
}
}
Viewcontroller2.swift
import UIKit
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
...
...
override func viewDidLoad() {
super.viewDidLoad()
...
var getdata = "\(appDelegate.Check)"
println("Check data : \(getdata)") // Output : Check data : Modified
}
}

Resources