Convenience init for UIViewController not appearing when added via extension - ios

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)
}
}

Related

Swift get value data from protocol

i need help with my code for swift 5,
so i make a struct and protocol to store list from uitextfield and now i wanna show that data in a UiTextView in another view controller
struct PatientNote {
var note : String
init(note :String) {
self.note = note
}
}
protocol AddNotesDelegate {
func AddNotes(controller : UIViewController, notes: PatientNote)
}
class AddNotesController: UIViewController {
var delegate : AddNotesDelegate!
#IBOutlet weak var Notes: UITextView!
#IBAction func addNotes(_ sender: Any) {
if let notes = self.Notes.text {
let patientNote = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: patientNote)
print(patientNote.note)
}
}
}
and now i wanna show in my view controller but i get this error of "Cannot convert value of type 'PatientNote' to expected argument type 'String'" in this viewController
class NotePatientController: UIViewController, AddNotesDelegate{
func AddNotes(controller: UIViewController, notes: PatientNote) {
let NotesPatient = PatientNote(note: notes) *this is where i get the error
}
var delegate : AddNotesDelegate!
var pasien : PatientNote!
override func viewDidLoad() {
super.viewDidLoad()
PatientTextView.text = pasien.note
}
#IBOutlet weak var PatientTextView: UITextView!
//in this ibaction i edit the notes that i get from the first Vc which is AddNotesController
#IBAction func Save(_ sender: UIButton) {
if let notes = self.PatientTextView.text {
let pasienNotes = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: pasienNotes)
}
}
}
i try to show the note from the AddNotesController to the NotePatientController, and in the NotePatientController i can edit and save the notes in UiTextView.
so i know i must be using the protocol in a wrong way, can someone help me how should i use it? im still kinda new in swift so could probably use any help i can get, Cheer!
Change let notesPatient = PatientNote(note: notes) to let notesPatient = PatientNote(note: notes.note)
It appears PatientNote takes a String as an argument but you passed an already created PatientNote to it instead. The below syntax, using notes.note would be a cleaner solution without involving initialising a new PatientNote.
func AddNotes(controller: UIViewController, notes: PatientNote) {
print(notes.note) // access the note String like this
}

Updating a UILabel via protocol results in crash (found nil)

I want to implement the MVP pattern for a new app. So the View shouldn't have any logic besides one that exclusively concerns UI elements. Therefore I want to request initial data from an "Interpreter" (interpreting user input in later code), which in turn requests data from my model and gives it to the "Presenter". The presenter holds a protocol with functions of the view.
The problem is: Calling updateUIData() from the presenter results in a
fatal error: unexpectedly found nil while unwrapping an Optional value
while calling the function from within the View at the same position is working just fine.
I suspect the error comes from the initialization of the specific MainViewController in the init of the presenter, but I don't know how to resolve this, if my guess is right.
Here's my (relevant) code:
MainViewController:
class MainViewController: UIViewController {
lazy var interpreter = Interpreter() // lazy needed b/c Interpreter holds Presenter which holds MainViewController
#IBOutlet var dateLabel: UILabel!
#IBOutlet var totalTimeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// updateUIData()
requestData()
}
func requestData() {
interpreter.requestData()
}
}
extension MainViewController: MainViewSetters {
func updateUIData() {
dateLabel.text = "Data"
totalTimeLabel.text = "loaded"
}
}
MainViewSetters (Protocol):
protocol MainViewSetters {
func updateUIData()
}
Interpreter:
class Interpreter {
let presenter = Presenter()
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
Presenter:
class Presenter {
let mainView: MainViewSetters
init(withMainViewController mainVC: MainViewSetters = MainViewController()) {
mainView = mainVC
}
func presentData() {
mainView.updateUIData()
}
}
Your problem here is that you are not passing the reference to MainViewController to your instance of Presenter.
This code :
lazy var interpreter = Interpreter()
Should be more like this : (Type is needed here because with lazy the compiler can't infer properly)
lazy var interpreter: Interpreter = Interpreter(for: self)
You then have to create a special initializer in Interpreter which will pass the viewController instance to its presenter property :
class Interpreter {
let presenter: Presenter
init(for viewController: MainViewSetters) {
presenter = Presenter(withMainViewController: viewController)
}
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
I also highly suggest you to remove the default value to Presenter's init method, it's very unlikely you'll want to assign a random instance of MainViewController as mainView of any Presenter object.
Finally, please note that this code is creating a retain cycle and neither your MainViewController instance nor your Presenter instance will be deallocated. This is due to the fact the Presenter class holds a strong reference to the MainViewController instance with its property mainView. To fix this you have to mark the mainView as weak as well as making it optional.
Please see the fixed implementation below :
class Presenter {
weak var mainView: MainViewSetters?
init(withMainViewController mainVC: MainViewSetters) {
mainView = mainVC
}
func presentData() {
mainView?.updateUIData()
}
}
For weak to be acceptable on a property of type MainViewSetters (which is not a real type but only a protocol) you have to specify that its a protocol that will only be applied to classes :
protocol MainViewSetters: class {
func updateUIData()
}
You are initializing interpreter passing a default MainViewController().
Change that code from:
lazy var interpreter = Interpreter()
to
lazy var interpreter = Interpreter(withMainViewController: self)

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)
}
}

What is wrong with my template/generic Swift initializer/constructor?

I created a SlidingNavigationController where I wanted to have an initializer that takes three parameters. All three parameters should be UIViewControllers but they need to confirm to my SlidingIconProtocol. So I wrote code like this (simplified version):
struct SlidingItem {
var bigIconView: UIView
var smallIconView: UIView
}
protocol SlidingIconProtocol {
var slidingItem: SlidingItem { get set }
}
class SlidingNavigationController: UIViewController {
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CentralVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class LeftVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class RightVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The problem is that Swift fails to compile on the last line of code with: "Cannot invoke initializer for type 'SlidingNavigationController' with an argument list of type '(centralVC: CentralVC, leftVC: LeftVC, rightVC: RightVC)'"
Not sure why this does not work, since even Swift/Xcode completion is giving me option to use this initializer. And all passed parameter confirm to SlidingIconProtocol.
Does anyone know what is wrong with the code and what is the right way in Swift to achieve the same (is it possible at all) ?
You can't use template like that way. In your code:
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T)
{
super.init(nibName: nil, bundle: nil)
}
T represents a class that is a subclass of UIViewController and implements SlidingIconProtocol. So when you call:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The T is assumed as CentralVC (first parameter), and the init method will be represented as:
init< CentralVC:UIViewController where CentralVC:SlidingIconProtocol>(centralVC: CentralVC, leftVC: CentralVC, rightVC: CentralVC)
{
super.init(nibName: nil, bundle: nil)
}
But you are passing different class object as the second and third parameter. And it will throw error. In your class the following code is valid:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: CentralVC(), rightVC: CentralVC())
Because all the passed arguments are object of same class (CentralVC). So fixing the issue, you need to implement the init method in the following way:
init<T1:UIViewController, T2:UIViewController, T3:UIViewController where T1:SlidingIconProtocol, T2:SlidingIconProtocol, T3:SlidingIconProtocol>(centralVC: T1, leftVC: T2, rightVC: T3)
{
super.init(nibName: nil, bundle: nil)
}

Swift Reference Variable from Specific Instance of Different Class

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
}
}

Resources