I've been reading up on ReactiveCocoa v3 lately and I'm struggling with just setting up basic stuff. I've already read the changelog, the tests, the few SO questions and the articles by Colin Eberhardt on the subject. However, I'm still missing examples on basic bindings.
Let's say I have an app that presents the menu of the day. The app is using RAC3 and the MVVM pattern.
Model (Menu)
The model has one simple method for fetching todays menu. As for now, this don't do any network requests, it basically just creates a model object. The mainCourse property is a String.
class func fetchTodaysMenu() -> SignalProducer<Menu, NoError> {
return SignalProducer {
sink, dispoable in
let newMenu = Menu()
newMenu.mainCourse = "Some meat"
sendNext(sink, newMenu)
sendCompleted(sink)
}
}
ViewModel (MenuViewModel)
The view model exposes different String variables for letting the view controller show the menu. Let's just add one property for showing the main course.
var mainCourse = MutableProperty("")
And we add a binding for this property:
self.mainCourse <~ Menu.fetchTodaysMenu()
|> map { menu in
return menu.mainCourse!
}
ViewController (MenuViewController)
Last but not least, I want to present this main course in a view. I'll add a UILabel for this.
var headline = UILabel()
And finally I want to set the text property of that UILabel by observing my view model. Something like:
self.headline.text <~ viewModel.headline.producer
Which unfortunately does not work.
Questions
The method fetchTodaysMenu() returns a SignalProducer<Menu, NoError>, but what if I want this method to return a SignalProducer<Menu, NSError> instead? This would make my binding in my view model fail as the method now may return an error. How do I handle this?
As mentioned, the current binding in my view controller does not work. I've been playing around with creating a MutableProperty that represents the text property of the UILabel, but I never got it right. I also think it feels clumsy or verbose to have to create extra variables for each property I want to bind. This was not needed in RAC2. I intentionally also tried to avoid using DynamicProperty, but maybe I shouldn't? I basically just want to find the right way of doing RAC(self.headline, text) = RACObserve(self.viewModel, mainCourse);.
Any other feedback/guidance on how to make this basic setup is highly appreciated.
So, after writing this question Colin Eberhardt made a part 3 of his RAC3 blog post series which includes a interesting and very relevant example of using MVVM and RAC3. The post can be found here and the source code here.
Based on his work, I've managed to answer my own questions:
By taking a slightly different approach, I'm able to make the fetchTodaysMenu() return a SignalProducer<Menu, NSError> as wanted. Here's how what I then would do in my view model:
MenuService.fetchTodaysMenu()
|> observeOn(QueueScheduler.mainQueueScheduler)
|> start(next: { response in
self.mainCourse.put(response.mainCourse!)
}, error: {
println("Error \($0)")
})
It seems like there's no UIKit bindings yet as of RAC3 beta 4. Colin made some UIKit extensions himself to help him make these bindings I was looking for as well. These can be found here. Adding them to my project, made be able to do exactly what I wanted to:
self.mainCourse.rac_text <~ self.viewModel.mainCourse
Update May 25, 2015
After been working a lot more with ReactiveCocoa 3, I would like to answer 1) once again. By using catch, it's possible to do this in a more declarative manner. I ended up implementing a small helper function for this:
public func ignoreError<T: Any, E: ErrorType>(signalProducer: SignalProducer<T, E>) -> SignalProducer<T, NoError> {
return signalProducer
|> catch { _ in
SignalProducer<T, NoError>.empty
}
}
The function transforms any NSError to NoError making it possible to bind as I wanted to by doing MenuService.fetchTodaysMenu() |> ignoreError.
I open sourced my project as this might be a good starting point for others looking into ReactiveCocoa 3.0:
https://github.com/s0mmer/TodaysReactiveMenu
Update March 5, 2016
As highlighted in the comments, since Swift 2, the ignoreError function would now look like:
public func ignoreError() -> SignalProducer<Value, NoError> {
return flatMapError { _ in
SignalProducer<Value, NoError>.empty
}
}
Also, an extension library called Rex has also been made, where something similar has been added.
Related
This is a swift question.
I have a question on getting the placeholder (or id, etc.) from a line XCUIApplication().someView.searchField.element.
I need to achieve this in order to get the text attributes of the elements that are tap()-ed in a test code.
Below is the exact code, gotten from the https://github.com/BalestraPatrick/Tweetometer/blob/a11c20244a37eacd4a36586f679f3ba184c66f86/Carthage/Checkouts/IGListKit/Examples/Examples-iOS/IGListKitExamples-UITests/SearchViewControllerUITests.swift.
import XCTest
final class SearchViewControllerUITests: UITestCase {
var collectionViews: XCUIElementQuery!
...
func test_whenSearchingForText_thatResultsGetFiltered() {
let searchField = collectionViews.searchFields.element <- ex) this element's placeholder.
searchField.tap()
searchField.typeText("tac")
let tacos = collectionViews.cells.staticTexts["tacos"]
let small = collectionViews.cells.staticTexts["small"]
XCTAssertTrue(tacos.exists)
XCTAssertFalse(small.exists)
}
}
The full code is available in the above link, but I am very new to Swift, so that I have no idea where to find the view and the element's site of declaration (something like .xml in android code?)
Actually, I am curious if this is possible in a static way (i.e. only looking at the code, not really exploring through the GUI).
Thanks in advance for any kind of hint!
I'm still trying to create a calendar app and ran into another problem I wasn't able to find a solution for online.
Xcode throws the error "Missing argument for parameter #1 in call" in line 2 of my code sample. I used similar code at other places before, but I can't find the difference in this one.
Also this code worked before and started throwing this error after I moved some code to the new View DayViewEvent after getting the error "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions", which I hoped to fix it and would be clean code anyway.
For reference: events is an optional array of EKEvents (hence the first if) and selectedDate is (obviously) a Date.
Any help is greatly appreciated!
if let events = events {
ForEach(events, id: \.self) { event in
if !event.isAllDay {
DayViewEvent(selectedDate: selectedDate, event: event)
}
}
}
The body func is called on every state change so needs to be as fast as possible thus the data needed should already be processed. So need to filter your events in a func or block, e.g. in onAppear:
struct EventsView: View {
#State var events = [Events]()
var body: some View {
ForEach(events, id: \.eventIdentifier) { event in
DayViewEvent(event: event)
}
.onAppear {
events = EventsDB.shared.events(isAllDay: false)
}
}
}
And by the way, since you are using id: \.self because event is a reference type you need to ensure when retrieving the events that the same event always returns the same instance and not a new instance. If unique instances are returned then you need to instead use an id that is a unique property, e.g. id: \.eventIdentifier.
Also you should know that the SwiftUI declarative syntax inside body is not like normal programming, the ifs and for loops all generate some very complex code, e.g. _ConditionalContent when an if is used, read up on it here: https://swiftwithmajid.com/2021/12/09/structural-identity-in-swiftui/ or watch Apple's WWDC video Demystify SwiftUI.
Background
I have a singleton class in my app, declared according following the one line singleton (with a private init()) in this blog post. Specifically, it looks like this:
#objc class Singleton {
static let Singleton sharedInstance = Singleton()
#objc dynamic var aProperty = false
private init() {
}
}
I would like to bind the state of aProperty to whether a menu item is hidden.
How I tried to solve the problem
Here are the steps I followed to do this:
Go to the Object Library in Interface Builder and add a generic "Object" to my Application scene. In the Identity inspector, configure "Class" to Singleton.
Create a referencing outlet in my App Delegate by Ctrl-dragging from the singleton object in Interface Builder to my App Delegate code. It ends up looking like this:
#IBOutlet weak var singleton: Singleton!
Go to the Bindings inspector for the menu item, choose "Hidden" under "Availability", check "Bind to", select "Singleton" in the combo box in front of it, and type aProperty under "Model Key Path".
The issue
Unfortunately, this doesn't work: changing the property has no effect on the menu item in question.
Investigating the cause
The issue appears to be that, despite declaring init() as private, Interface Builder is managing to create another instance of my singleton. To prove this, I added NSLog("singleton init") to the private init() method as well as the following code to applicationDidFinishLaunching() in my app delegate:
NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")
When I run the app, this is output in the logs:
singleton init
singleton init
sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)
Therefore, there are indeed two different instances. I also added this code somewhere else in my app delegate:
NSLog("aProperty: [\(singleton!.aProperty),\(String(describing:singleton!.value(forKey: "aProperty"))),\(Singleton.sharedInstance.singleton),\(String(describing:Singleton.sharedInstance.value(forKey: "aProperty")))] hidden: \(myMenuItem.isHidden)")
At one point, this produces the following output:
aProperty: [false,Optional(0),true,Optional(1)] hidden: false
Obviously, being a singleton, all values should match, yet singleton produces one output and Singleton.sharedInstance produces a different one. As can be seen, the calls to value(forKey:) match their respective objects, so KVC shouldn't be an issue.
The question
How do I declare a singleton class in Swift and wire it up with Interface Builder to avoid it being instantiated twice?
If that's not possible, how else would I go about solving the problem of binding a global property to a control in Interface Builder?
Is an MCVE necessary?
I hope the description was detailed enough, but if anyone feels an MCVE is necessary, leave a comment and I'll create one and upload to GitHub.
I just want to start my answer by stating that singletons should not be used for sharing global state. While they might seem easier to use in the beginning, they tend to generate lots of headaches later on, since they can be changed virtually from any place, making your program unpredictable some times.
That being said, it's not impossible to achieve what you need, but with a little bit of ceremony:
#objc class Singleton: NSObject {
// using this class behind the scenes, this is the actual singleton
class SingletonStorage: NSObject {
#objc dynamic var aProperty = false
}
private static var storage = SingletonStorage()
// making sure all instances use the same storage, regardless how
// they were created
#objc dynamic var storage = Singleton.storage
// we need to tell to KVO which changes in related properties affect
// the ones we're interested into
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
switch key {
case "aProperty":
return ["storage.aProperty"]
default: return super.keyPathsForValuesAffectingValue(forKey: key)
}
}
// and simply convert it to a computed property
#objc dynamic var aProperty: Bool {
get { return Singleton.storage.aProperty }
set { Singleton.storage.aProperty = newValue }
}
}
Unfortunately you can’t return a different instance from init in Swift.
Here are some possible workarounds:
Make an outlet for an instance of your class in Interface Builder and then only reference that instance throughout your code. (Not a singleton per se, but you could add some runtime checks to make sure it’s only instantiated from a nib file and not from code).
Create a helper class for use in Interface Builder and expose your singleton as its property. I.e. any instance of that helper class will always return a single instance of your singleton.
Make an Objective-C subclass of your Swift singleton class and make its init's always return a shared Swift singleton instance.
There is a way around the problem in my particular case.
Recall from the question that I only wanted to hide and unhide a menu according to the state of aProperty in this singleton. While I was attempting to avoid write as much code as possible, by doing everything in Interface Builder, it seems in this case it's much less hassle to just write the binding programmatically:
menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)
I am going through the tutorial:
https://marcosantadev.com/mvvmc-with-swift/
Which talks about MVVM-C design pattern. I have real trouble understanding of how and why .never() observable is used there (and in general why we would want to use .never() besides testing timeouts).
Could anyone give a reasonable example of .never() observable usage in swift code (not in testing) and explain why it is necessary and what are the alternatives?
I address all the actions from View to ViewModel. User taps on a button? Good, the signal is delivered to a ViewModel. That is why I have multiple input observables in ViewModel. And all the observables are optional. They are optional because sometimes I write tests and don't really want to provide all the fake observables to test some single function. So, I provide other observables as nil. But working with nil is not very convenient, so I provide some default behavior for all the optional observables like this:
private extension ViewModel {
func observableNavigation() -> Observable<Navigation.Button> {
return viewOutputFactory().observableNavigation ?? Observable.never()
}
func observableViewState() -> Observable<ViewState> {
return viewOutputFactory().observableViewState ?? Observable.just(.didAppear)
}
}
As you can see, if I pass nil for observableViewState I substitute it with just(.didAppear) because the ViewModel logic heavily depends on the state of view. On the other hand if I pass nil for observableNavigation I provide never() because I assume that non of the navigation button will ever be triggered.
But this whole story is just my point of view. I bet you will find your own place to use this never operator.
Maybe your ViewModel has different configurations (or you have different viewModel under the same protocol), one of which does not need to send any updates to its observers. Instead of saying that the observable does not exist for this particular case (which you would implement as an optional), you might want to be able to define an observable as a .never(). This is in my opinion cleaner.
Disclaimer - I am not a user of RxSwift, but I am assuming never is similar than in ReactiveSwift, i.e. a signal that never sends any value.
It's an open ended question, and there can be many answers, but I've found myself reaching for never on a number of cases. There are many ways to solve a problem, but recently, I was simplifying some device connection code that had a cascading fail over, and I wanted to determine if my last attempt to scan for devices yielded any results.
To do that, I wanted to create an observable that only emitted a "no scan results" event in the event that it was disposed without having seen any results, and conversely, emitted nothing if it did.
I have pruned out other details from my code to sake of brevity, but in essence:
func connect(scanDuration: TimeInterval) -> Observable<ConnectionEvent> {
let scan = scan(for: scanDuration).share(replay: 1)
let connection: Observable<ConnectionEvent> =
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan)
.flatMapLatest { [retainedSelf = self] in retainedSelf.connect(to: $0) }
let scanDetector = scan
.toArray() // <-- sum all results as an array for final count
.asObservable()
.flatMap { results -> Observable<ConnectionEvent> in
results.isEmpty // if no scan results
? Observable.just(.noDevicesAvailable) // emit event
: Observable.never() } // else, got results, no action needed
// fold source and stream detector into common observable
return Observable.from([
connection
.filter { $0.isConnected }
.flatMapLatest { [retained = self] event -> Observable<ConnectionEvent> in
retained.didDisconnect(peripheral: event.connectedPeripheral!.peripheral)
.startWith(event) },
scanDetector])
.switchLatest()
}
For a counter point, I realized as I typed this up, that there is still a simpler way to achieve my needs, and that is to add a final error emitting observable into my concat, it fails-over until it hits the final error case, so I don't need the later error detection stream.
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan,
hardFailureEmitNoScanResults())
That said, there are many cases where we may want to listen and filter down stream, where the concat technique is not available.
I would like to be able to annotate my types and methods with meta-data and read those at runtime.
The language reference explains how to declare attribute usages, but is it actually possible to declare your own attributes?
Reading would require some kind of reflection mechanism, which I was not able to find in the reference at all, so the second part of the question probably is - is there reflection possible. If these features are not available in Swift, can they be done with Objective-C code (but on Swift instances and types)?
A relatively unrelated note: The decision of what has been modelled as an attribute and what has been added to the core syntax strikes me as pretty arbitrary. It feels like two different teams worked on the syntax and on some attributes. E.g. they put weak and unowned into the language as modifiers, but made #final and #lazy attributes. I believe that once they actually add access modifiers, they will probably be attributes likes final. Is all of this somehow related to Objective-C interoperability?
If we take the iBook as definitive, there appears to be no developer-facing way of creating arbitrary new attributes in the way you can in Java and .NET. I hope this feature comes in later, but for now, it looks like we're out of luck. If you care about this feature, you should file an enhancement request with Apple (Component: Swift Version: X)
FWIW, there's really not a way to do this in Objective-C either.
You can now do something like this! Check out "Property Wrappers" - https://docs.swift.org/swift-book/LanguageGuide/Properties.html
Here's an example from that page:
#propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
struct SmallRectangle {
#TwelveOrLess var height: Int
#TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"