How does RxSwift work if you click a UIButton? - swift

I've just started learning RxSwift. I've added a UIButton with an observable in and a suscribe method that should trigger everytime data changes, but I don't get it to work. What am I missing?
#IBAction func buttonAction(_ sender: Any) {
publishableSubject.onNext("GURKA")
}
override func viewDidLoad() {
super.viewDidLoad()
let bag = DisposeBag()
_ = publishableSubject.subscribe(onNext: {
print($0)
}).disposed(by: bag)
}

Your dispose bag is local so it de-inits when viewDidLoad exits and that will dispose the observable chain you setup. For what you describe, just moving the dispose bag to the class level should fix it.
let publishableSubject = PublishSubject<String>()
let bag = DisposeBag()
#IBAction func buttonAction(_ sender: Any) {
publishableSubject.onNext("GURKA")
}
override func viewDidLoad() {
super.viewDidLoad()
publishableSubject
.subscribe(onNext: {
print($0)
})
.disposed(by: bag)
}
You might want to consider importing the RxCocoa library as well. It sets up the action for you so you can write less code:
override func viewDidLoad() {
super.viewDidLoad()
myButton.rx.tap
.map { "GURKA" }
.subscribe(onNext: {
print($0)
})
.disposed(by: bag)
}

Related

Subclassed NSView to notify ViewController of action

I have subclassed NSView to receive a dropped folder in order to get its URL.
I've been getting the URL to my ViewController class by accessing a property set in my custom NSView class.
import Cocoa
class DropView: NSView {
var droppedURL : URL!
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
public required init?(coder: NSCoder) {
super .init(coder: coder)
registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
}
public override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return NSDragOperation.copy
}
public override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
NSDragOperation.copy
}
public override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pboard = sender.draggingPasteboard
let urlString = pboard.string(forType: NSPasteboard.PasteboardType.fileURL)
let folderURL = URL(string: urlString!)
print(folderURL)
droppedURL = folderURL
return true
}
}
How can I let my ViewController know when a folder has been dropped onto my NSView and that a URL has been successfully captured? Is there a way other than posting a notification?
Usually you'd use delegates or closures for this. I prefer closures because they're so clean, but it's up to you.
First, define your closure in DropView:
class DropView: NSView {
var droppedURL : URL!
var droppedSuccessfully: ((URL) -> Void)? /// here!
Then, call it just like how you'd call a function. Make sure to pass in your folderURL too.
public override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pboard = sender.draggingPasteboard
let urlString = pboard.string(forType: NSPasteboard.PasteboardType.fileURL)
let folderURL = URL(string: urlString!)
print(folderURL)
droppedURL = folderURL
droppedSuccessfully?(folderURL) /// here!
return true
}
Finally, assign the closure back in your ViewController.
override func viewDidLoad() {
super.viewDidLoad()
...
/// prevent retain cycle
yourDropView.droppedSuccessfully = { [weak self] url in
print("URL received: \(url)")
}
}

Stop listening to NSEvent in Swift

I am trying to get NSEvent to stop listening to local event. Is there a way to access the event after creating it?(which is what I unsuccessfully tried in removeEvent)
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: sayHello(event:))
}
func sayHello(event: NSEvent) -> NSEvent {
print("Welcome")
return event
}
#IBAction func removeEvent(_ sender: NSButton) {
let event = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: sayHello(event:))
NSEvent.removeMonitor(event)
}
Just create a reference to your event monitor adding an instance property to your view controller and remove it with your button action:
var monitor: Any?
func sayHello(_ event: NSEvent) -> NSEvent {
print(#function)
return event
}
override func viewDidLoad() {
super.viewDidLoad()
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: sayHello)
}
#IBAction func removeEvent(_ sender: NSButton) {
if let monitor = monitor {
NSEvent.removeMonitor(monitor)
}
}

What's the replacements of override in Rx?

I'm new to rx and curious about a question: What's the replacement of override in Rx?
For codes I have read about rx, a button is configured like:
override func viewDidLoad() {
super.viewDidLoad()
updateConversation()
self.naviAvatar.rx.tap
.debug("naviAvatar tap")
.subscribe(onNext: { _ in
print("didTapNaviAvatar")
})
.disposed(by: disposeBag)
}
and it works perfect.
However I meet a question that in a subclass I want to silent the button and I don't know how to achieve in rx.
In my previous code, I have following codes:
class A: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapNaviAvatar(_:)))
self.naviAvatar.addGestureRecognizer(tapRecognizer)
}
#objc func didTapNaviAvatar(_ sender: Any) {
print("didTapNaviAvatar")
}
//...
}
class B: A {
// Silent the method, do nothing.
override func didTapNaviAvatar(_ sender: Any) {}
//...
}
I came up with an idea that I can reconfigure the naviAvatar in B's viewDidLoad method. But what if I have number of codes(like 20 lines, including mapping, filtering, configuring) about the button's behavior but I just want to change only one line(like just override the button title on touch down)?
Any helps would be appreciated.
Weak self isn't needed because there is no delay or async method fired on a tap action. So you can just pass the method and it will trigger the method overridden in class B.
class A: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
naviAvatar.rx.tap
.debug("naviAvatar tap")
.subscribe(onNext: didTapNaviMoreButton)
.disposed(by: disposeBag)
}
#objc func didTapNaviAvatar(_ sender: Any) {
print("didTapNaviAvatar")
}
}
class A: B {
override func didTapNaviAvatar(_ sender: Any) {}
}

how I should send data from xib to viewcontroller?

i have a view that is a xib, this view contains a buttons for a calculator, I did this because I need a custom keyboard for my app, my app has some views that needs this keyboard
the problem is, how I send the data from view to my view controller?
in my view controller I only has a textfield and a view with xib custom class
I use swift 4.2
custom view and textflied
this is my code from my xib
import UIKit
class keyboardView: UIView {
#IBOutlet var viewKeyboard: UIView!
var textIntroduced = ""
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
custom()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
custom()
}
private func custom()
{
Bundle.main.loadNibNamed("keyboard", owner: self, options: nil)
viewKeyboard.frame = self.bounds
viewKeyboard.autoresizingMask = [.flexibleHeight,.flexibleWidth]
for view in viewKeyboard.subviews
{
view.isExclusiveTouch = true
}
addSubview(viewKeyboard)
}
#IBAction func addOne(_ sender: Any) {
textIntroduced += "1"
}
#IBAction func addTwo(_ sender: Any) {
textIntroduced += "2"
}
#IBAction func addThree(_ sender: Any) {
textIntroduced += "3"
}
#IBAction func addFour(_ sender: Any) {
textIntroduced += "4"
}
#IBAction func addFive(_ sender: Any) {
textIntroduced += "5"
}
#IBAction func addSix(_ sender: Any) {
textIntroduced += "6"
}
#IBAction func addSeven(_ sender: Any) {
textIntroduced += "7"
}
#IBAction func addEight(_ sender: Any) {
textIntroduced += "8"
}
#IBAction func addNine(_ sender: Any) {
textIntroduced += "9"
}
#IBAction func addZero(_ sender: Any) {
textIntroduced += "0"
print(textIntroduced)
}
#IBAction func removeNumber(_ sender: Any) {
textIntroduced.removeLast()
}
}
in my view controller I only has a textfield and view with custom class
i want to push any button view and the result should be written in the textfield.
You can use protocol to observe data changes in your xib. First of all you need to create a protocol like this.
protocol NumberCalculable: class {
func addNumber(_ number: Int)
}
Then, inside your xib file below your viewKeyboard outlet you need to create a delegate for your protocol.
weak var delegate: NumberCalculable?
Instead of doing textIntroduced += 1 you should change with this delegate?.addNumber(1) and same for other IBAction methods.
Thirdly, you need to conform your protocol in your viewController class doing
keyboardView.delegate = self inside viewDidLoad() method.
extension ViewController: NumberCalculable {
func addNumber(_ number: Int) {
// do whatever you want
}
}
Hope this helps.

Swift App runs but no buttons appear

I wrote a Swift app but only the window appears when it runs. I can't see any buttons.
Here is my code... I've tried removing the .white attribute thinking maybe it was hidden behind a layer. Nothing.
//
// ViewController.swift
// BraviaRemote
//
// Created by Ed Gilroy on 7/2/17.
// Copyright © 2017 Edward Williams. All rights reserved.
//
import Cocoa
import Alamofire
class ViewController: NSViewController, NSTextFieldDelegate {
#IBAction func MenuButton(_ sender: NSButtonCell) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAABgAw==")
}
#IBAction func ReturnButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAgAAAJcAAAAjAw==")
}
#IBAction func InfoButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAA6Aw==")
}
#IBAction func GuideButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAgAAAKQAAABbAw==")
}
#IBAction func SelectButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAABlAw==")
}
#IBAction func ChnUpButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAAQAw==")
}
#IBAction func ChnDownButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAARAw==")
}
#IBAction func VolUpButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAASAw==")
}
#IBAction func VolDownButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAATAw==")
}
#IBAction func LeftButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAA0Aw==")
}
#IBAction func RightButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAAzAw==")
}
#IBAction func UpButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAB0Aw==")
}
#IBAction func DownButton(_ sender: NSButton) {
triggerRemoteControl(irccc: "AAAAAQAAAAEAAAB1Aw==")
}
#IBAction func OnOffButton(_ sender: NSSegmentedControl){
}
#IBOutlet weak var IPField: NSTextField!
var IPAddress: String? {
didSet {
if IPField != nil { IPAddress = "http://\(IPAddress!)/sony/IRCC?" }
else {IPAddress = "http://192.168.2.7/sony/IRCC?"}
if let ip = IPAddress { print (ip) } //Unwraps optional
}
}
override func controlTextDidChange(_ obj: Notification) {
if let txtField = obj.object as? NSTextField {
if txtField.tag == 0 {
//Validation (for later)
IPAddress = txtField.stringValue
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
func viewDidLoad() {
super.viewDidLoad()
}
}
override func viewDidAppear() {
// Window Properties, including solid colour, lack of resize, movable by background.
view.window?.titlebarAppearsTransparent = true
view.window?.backgroundColor = NSColor.white
view.window?.styleMask.remove(.resizable)
view.window?.isMovableByWindowBackground = true
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
struct SOAPEncoding: ParameterEncoding {
let service: String
let action: String
let IRCCC: String
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard parameters != nil else { return urlRequest }
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("text/xml", forHTTPHeaderField: "Content-Type")
}
let soapBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body><u:\(action) xmlns:u=\"\(service)\"><IRCCCode>\(IRCCC)</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>"
urlRequest.httpBody = soapBody.data(using: String.Encoding.utf8)
return urlRequest
}
}
func triggerRemoteControl(irccc: String) {
Alamofire.request(IPAddress!,
method: .post,
parameters: ["parameter" : "value"],
encoding: SOAPEncoding(service: "urn:schemas-sony-com:service:IRCC:1",
action: "X_SendIRCC", IRCCC: irccc)).responseString { response in
print(response)
}
}
}
Three errors:
First, you are overriding viewDidLoad() and defining another viewDidLoad() inside of it.
Your code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
func viewDidLoad() {
super.viewDidLoad()
}
}
Should just look like this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
Second, you are overriding viewDidAppear but never calling super.
Your code:
override func viewDidAppear() {
// Window Properties, including solid colour, lack of resize, movable by background.
view.window?.titlebarAppearsTransparent = true
view.window?.backgroundColor = NSColor.white
view.window?.styleMask.remove(.resizable)
view.window?.isMovableByWindowBackground = true
}
Should look like this:
override func viewDidAppear() {
super.viewDidAppear()
// Window Properties, including solid colour, lack of resize, movable by background.
view.window?.titlebarAppearsTransparent = true
view.window?.backgroundColor = NSColor.white
view.window?.styleMask.remove(.resizable)
view.window?.isMovableByWindowBackground = true
}
Third, you are overriding the IPAdress didSet and then setting it again. This will cause an infinite loop. You are also comparing a textField to nil, which it will never be, because it's a NSTextField!, instead of checking whether it's empty or not. I can't really make sense of what you're trying to achieve here but you should rip all this overriding nonsense out until you can clearly formulate your intention.