Binding dose not seem to be working from ReactiveCocoa Swift - ios

var viewModel = CTCViewModel()
var mainView: CTCMainView {
return self.view as! CTCMainView
}
override func viewDidLoad() {
super.viewDidLoad()
let callButtonEnabledSignal = self.viewModel.rac_valuesForKeyPath("callButtonEnabled", observer: self.viewModel)
callButtonEnabledSignal.setKeyPath("enabled", onObject: self.mainView.callButton, nilValue: false)
self.mainView.callButton.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNext {
(Void) -> Void in
self.viewModel.callButtonEnabled = !self.viewModel.callButtonEnabled
}
}
When I press the button, self.viewModel.callButtonEnabled did get updated but the enabled property of the button. It does not seem they are bound.

Add dynamic on your callButtonEnabled property:
class CTCViewModel: NSObject {
dynamic var callButtonEnabled = false
}
Because the implementation of rac_valuesForKeyPath is using Objective-C runtime, and the compiler can omit it when access Swift properties. You mark a property with dynamic to let the compiler always use Objective-C runtime.

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
}

add variable to all UIViewControllers

I'm new to Swift and I'm trying to implement a custom UIKeyCommand architecture in a practice app. I wrote the extension below for the base UISplitViewController to show all UIKeyCommands in the current views on screen.
extension UISplitViewController {
open override var canBecomeFirstResponder: Bool {
return true
}
var BPKeyCommands: [BPKeyCommand]? {
var commands: [BPKeyCommand] = []
var mastervc = self.viewControllers.first
if (mastervc is UINavigationController) {
mastervc = (mastervc as! UINavigationController).viewControllers.last
}
if let masterCommands = mastervc.commands {
for command in masterCommands {
commands.append(command)
}
}
return commands
}
open override var keyCommands: [UIKeyCommand]? {
var commands: [UIKeyCommand] = []
if let bpkeycommands = BPKeyCommands {
for command in bpkeycommands {
let new = UIKeyCommand(input: command.input,
modifierFlags: command.modifierFlags,
action: #selector(executeKeyCommand(sender:)),
discoverabilityTitle: command.title)
commands.append(new)
}
}
return commands
}
#objc private func executeKeyCommand(sender: UIKeyCommand) {
if let index = keyCommands?.firstIndex(of: sender) {
if let command = BPKeyCommands?[index] {
command.action(command)
}
}
}
}
Now, as you might expect this throws an error at if let masterCommands = mastervc.commands {, because UIViewController doesn't contain the commands variable out of the box. My question is: how can I haveUIViewControllerhave that variable? Just like all controllers can overridekeyCommands` by default?
You have to create a protocol with command variable and make your view controller conform to it (step 1). Either you can provide values for particular view controller or you can provide a default implementation.
step 1:- Create a protocol with the variable you need.
protocol Commandable{
var commands: [String]{set get}
}
extension Commandable{
var commands: [String]{
get{return ["hello","world"]}
set{}
}
}
step 2:- Make then controllers which you are using conform it
step 3:- change the above code to get commands
if let commandProtocol = masterVC as? Commandable
{
let commands = commandProtocol.commands
}
else{
// handle it
}
Make sure the variable is unique so you don't accidentally override it.
Thank you.
you can create a extension of UIViewController and add that property on that extension of UIViewController. Then You will get it on child view controllers like UISplitViewController or any other custom ViewControllers. To know more about extensions, Which can be added on extension or what can be done by extension??

swift:Unbind listeners which are closures from array of listeners

I am trying to develop a theme engine, which loads themes from a json. I have a Thememanager which is a singleton class and holds a currentTheme variable.
I then have a baseViewController which listens to any change in the currentTheme with the help of Boxing technique, and all the viewControllers need to be subclass of
base and need to override the observer method to apply their styles. In the box class I have an array of listeners so that multiple view controllers can observer theme change simultaneously, it works well and now
my problem is that whenever a view controller gets deallocated, I want to remove that listener also from the box class array of listeners, which I am unable to figure out, because of which listeners are getting piled up.
I tried to write an unbind method in the deint of the viewController and tried to pass the closure like the below but it didnt work
func unbind(listener: Listener?) {
self.listeners = self.listeners.filter { $0 as AnyObject !== listener as AnyObject }
}
Thememanager
class Thememanager {
// Hold a list of themes
var themes = [Theme]()
// Private Init
private init() {
fetchMenuItemsFromJSON()
// You can provide a default theme here.
//change(theme: defaultTheme)
}
// MARK: Shared Instance
private static let _shared = Thememanager()
// MARK: - Accessors
class func shared() -> Thememanager {
return _shared
}
var currentTheme: Box<Theme?> = Box(nil)
func change(theme: Theme) {
currentTheme.value = theme
}
private func fetchMenuItemsFromJSON() {
// TRIAL
let theme = Theme()
themes.append(theme)
}
}
BOX
class Box<T> {
typealias Listener = (T) -> Void
var listeners = [Listener?]()
var value: T {
didSet {
for listener in listeners{
listener?(value)
}
}
}
init(_ value: T) {
self.value = value
}
func bind(listener: Listener?) {
self.listeners.append(listener)
for listener in listeners{
listener?(value)
}
}
func unbind(listener: Listener?) {
self.listeners = self.listeners.filter { $0 as AnyObject !== listener as AnyObject }
}
}
BaseViewController
class BaseViewController: UIViewController {
private var themeManager = Thememanager.shared()
typealias Listener = (Theme?) -> Void
var currentListener: Listener?
override func viewDidLoad() {
super.viewDidLoad()
observeThemeChange()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Bind the theme variable so that changes are immediately effective
func observeThemeChange() {
currentListener = {[weak self] (theme) in
guard let currtheme = theme else {
return
}
self?.loadWith(theme: currtheme)
}
themeManager.currentTheme.bind(listener: currentListener)
}
// This method will be implemented by the Child classes
func loadWith(theme: Theme) {
self.navigationController?.navigationBar.tintColor = theme.navigationBarTextColor
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : theme.navigationBarTextColor]
// Need to be implemented by child classes
print("theme changed")
}
deinit {
themeManager.currentTheme.unbind(listener: currentListener)
}
}
Theme
struct Theme {
// Define all the theme properties you want to control.
var navigationBarBgColor: UIColor = UIColor.darkGray
var navigationBarTextColor: UIColor = UIColor.black
}
The issue is with the comparison of closure in unbind method as it snot available for closures and functions(). See this. I guess you could maintain a hashmap where listeners be the value and a unique identifier be the key. It is going to be much faster to unbind it as well.
But, I feel Notifications way is much better as it gives you the same behaviour(Publisher-Subscriber) without you having to manage the listeners.

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

#obj protocol delegate method not working in second class. Swift

Im trying to call protocol delegate in an additional class. The first class (ViewController) works but the second one I have implemented it in doesn't show any data. I added it with the autofill option so its not giving errors. It just doesn't do anything.
sender class
#objc protocol BWWalkthroughViewControllerDelegate{
#objc optional func walkthroughPageDidChange(pageNumber:Int) // Called when current page changes
}
#objc class BWWalkthroughViewController: UIViewController, UIScrollViewDelegate, ViewControllerDelegate {
// MARK: - Public properties -
weak var delegate:BWWalkthroughViewControllerDelegate?
var currentPage:Int{ // The index of the current page (readonly)
get{
let page = Int((scrollview.contentOffset.x / view.bounds.size.width))
return page
}
}
// MARK: - Private properties -
let scrollview:UIScrollView!
var controllers:[UIViewController]!
var lastViewConstraint:NSArray?
var shouldCancelTimer = false
var aSound: AVAudioPlayer!
var isForSound: AVAudioPlayer!
var alligatorSound: AVAudioPlayer!
var audioPlayer = AVAudioPlayer?()
var error : NSError?
var soundTrack2 = AVAudioPlayer?()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var audioPlayerAnimalSound = AVAudioPlayer?()
var audioPlayerAlphabetSound = AVAudioPlayer?()
var audioPlayerFullPhraze = AVAudioPlayer?()
var audioPlayerPhonics = AVAudioPlayer?()
Code removed to save space carries on:
/**
Update the UI to reflect the current walkthrough situation
**/
private func updateUI(){
// Get the current page
pageControl?.currentPage = currentPage
// Notify delegate about the new page
delegate?.walkthroughPageDidChange?(currentPage)
}
receiver class
class BWWalkthroughPageViewController: UIViewController, BWWalkthroughPage, ViewControllerDelegate, BWWalkthroughViewControllerDelegate {
Function in second Class.
func walkthroughPageDidChange(pageNumber: Int) {
println("current page 2 \(pageNumber)")
}
walkthroughPageDidChange does work in the viewController class however. Please can you help me see what is wrong?
Your weak var delegate:BWWalkthroughViewControllerDelegate? is an optional variable and it is not assigned anywhere in your presented code, hence it will be nil. You have to instantiate it somewhere for it to work.
A good way to do so is:
class BWWalkthroughViewController {
let bwWalkthroughPageViewController = BWWalkthroughPageViewController()
var bwWalkthroughViewControllerDelegate : BWWalkthroughViewControllerDelegate!
init() {
bwWalkthroughViewControllerDelegate = bwWalkthroughPageViewController as BWWalkthroughViewControllerDelegate
}
}
A thing to note is that it is often good practice to name the delegates with meaningful names. The keyword "delegate" is often used for many classes and is restricted. A more verbose name will eliminate the chance of overriding an original delegate and will help identify each one if you have more than one.

Resources