Scenario:
I'm translating an Objective-C sample code supplied by Apple into Swift 3.0.1.
I came across some code that requires the need to prevent the presentationController from being released prior to calling preventViewController. Hence the use of NS_VALID_UNTIL_END_OF_SCOPE (see image below).
What's the best alternative using Swift?
...without it... all I get is a nil for the transitioningDelegate value upon access soon afterwards.
One thing you can try is using withExtendedLifetime(_:_:):
override func perform() {
let sourceViewController = self.destination
let destinationViewController = self.destination
// For presentations which will use a custom presentation controller,
// it is possible for that presentation controller to also be the
// transitioningDelegate.
//
// transitioningDelegate does not hold a strong reference to its
// destination object. To prevent presentationController from being
// released prior to calling -presentViewController:animated:completion:
// the NS_VALID_UNTIL_END_OF_SCOPE attribute is appended to the declaration.
let presentationController = AAPLAdaptivePresentationController(presentedViewController: destinationViewController, presenting: sourceViewController)
withExtendedLifetime(presentationController) {
destinationViewController.transitioningDelegate = presentationController
self.source.present(destinationViewController, animated: true, completion: nil)
}
}
Or else, this would work in the case shown in the picture:
override func perform() {
let sourceViewController = self.destination
let destinationViewController = self.destination
// For presentations which will use a custom presentation controller,
// it is possible for that presentation controller to also be the
// transitioningDelegate.
//
// transitioningDelegate does not hold a strong reference to its
// destination object. To prevent presentationController from being
// released prior to calling -presentViewController:animated:completion:
// the NS_VALID_UNTIL_END_OF_SCOPE attribute is appended to the declaration.
let presentationController = AAPLAdaptivePresentationController(presentedViewController: destinationViewController, presenting: sourceViewController)
destinationViewController.transitioningDelegate = presentationController
self.source.present(destinationViewController, animated: true, completion: {
let _ = presentationController //<- having a strong reference in the completion handler
})
}
Related
I would like to perform a test in one of my ViewModels that contains a BehaviorRelay object called "nearByCity" that it is bind to BehaviorRelay called "isNearBy". That's how my view model looks like.
class SearchViewViewModel: NSObject {
//MARK:- Properties
//MARK: Constants
let disposeBag = DisposeBag()
//MARK: Vars
var nearByCity:BehaviorRelay<String?> = BehaviorRelay(value: nil)
var isNearBy = BehaviorRelay(value: true)
//MARK:- Constructor
init() {
super.init()
setupBinders()
}
}
//MARK:- Private methods
private extension SearchViewViewModel{
func setupBinders(){
nearByCity
.asObservable()
.distinctUntilChanged()
.map({$0 ?? ""})
.map({$0 == ""})
.bind(to: isNearBy)
.disposed(by: disposeBag)
}
}
The test that i want to perform is to actually verify that when the string is accepted, the bool value also changes according to the function setupBinders().
Any Idea?
Thank you
Here's one way to test:
class RxSandboxTests: XCTestCase {
func testBinders() {
let scheduler = TestScheduler(initialClock: 0)
let source = scheduler.createColdObservable([.next(5, "hello"), .completed(10)])
let sink = scheduler.createObserver(Bool.self)
let disposeBag = DisposeBag()
let viewModel = SearchViewViewModel(appLocationManager: StubManager())
source.bind(to: viewModel.nearByCity).disposed(by: disposeBag)
viewModel.isNearBy.bind(to: sink).disposed(by: disposeBag)
scheduler.start()
XCTAssertEqual(sink.events, [.next(0, true), .next(5, false)])
}
}
Some other points:
Don't make your subject properties var use let instead because you don't want anybody to be able to replace them with unbound versions.
The fact that you have to use the AppLocationManager in this code that has no need of it implies that the object is doing too much. There is nothing wrong with having multiple view models in a view controller that each handle different parts of the view.
Best would be to avoid using Subjects (Relays) at all in your view model code, if needed, they are better left in the imperative side of the code.
At minimum, break up your setupBinders function so that the parts are independently testable. Your above could have been written as a simple, easily tested, free function:
func isNearBy(city: Observable<String?>) -> Observable<Bool> {
return city
.distinctUntilChanged()
.map {$0 ?? ""}
.map {$0 == ""}
}
currently my output is displaying selected values from pickerview inside textfields.
now my question is i want to access all these values on submit button and want to display in another view controller how to do this?. let me explain my scenario my first vc is set as collectionview from one of the collectionviewcell m redirecting to this page.
Note: i already know that how to pass data between two view controller. but its not working in my case.Please Help.
Code
#IBAction func StaffAtten_Action(_ sender: Any) {
// let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "StaffAttendence_SecondPage") as! StaffAttendence_SecondPage
//
// secondVC.a = active_text.text!
// secondVC.b = active_text.text!
// secondVC.c = active_text.text!
// secondVC.savedata.append(year.text!)
// secondVC.savedata.append(month.text!)
// secondVC.savedata.append(institute.text!)
}
}
The problem that you are having is that you are instantiating a brand new VC and passing data to it. The VC that is actually presented is not the one you created.
Since you have a segue connecting the two VCs, override prepare(for:sender:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? StaffAttendence_SecondPage {
vc.a = ...
vc.b = ...
// pass the rest of the data here...
}
}
I'm making a new Swift (3.0) framenwork which involves the use of a NSSearchField programmatically created, like the rest of the views.
This framenwork has the deployment target set to 10.10 and I found strange being not able to set the delegate metods for it. In fact NSSearchFieldDelegate only appear availabe in 10.11 on.
class PlistEditor: NSObject, NSOutlineViewDelegate, NSOutlineViewDataSource, NSTextFieldDelegate, NSSearchFieldDelegate {
var searchField : NSSearchField?
// more code..
init(tabItem: NSTabViewItem,
plistPath: String?) {
// more code..
let sf = NSSearchField(frame: NSMakeRect(80, 0, 80, 22))
self.searchField? = sf
// more code..
super.init()
// more code..
if #available(OSX 10.11, *) {
self.searchField?.delegate = self
} else {
// Fallback on earlier versions
}
// more code
}
}
Ok, I thought it was inherent from NStextField, and maybe I can access the cell and set up using the superclass delegate, but unfurtunately I cannot found a way to do that.
What I need is to be able to receive NSText/NSTextField notification in 10.10. How can I do this?
EDITED:
added more info on how is made plus some picts
Without providing more info, i am guessing you forgot to declare that your class is conforming to the NSSearchFieldDelegate.
See the example below, how to set it up for example a viewController. You just create an extension for your vc, and declare it to conform to the delegate.
class SearchViewController: NSViewController {
let searchField: NSSearchField? = NSSearchField(frame: .zero)
override func viewWillAppear() {
super.viewWillAppear()
if #available(OSX 10.11, *) {
self.searchField?.delegate = self
} else {
// Fallback on earlier versions
}
}
}
extension SearchViewController: NSSearchFieldDelegate {
func searchFieldDidStartSearching(_ sender: NSSearchField) {
print("didStart")
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
print("didEnd")
}
}
EDIT:
To capture the textDidChange event in earlier versions of Mac OS, you need to subclass NSSearchField and override textDidChange function. Every time a change happens in the searchField, it will call the function for you.
class DLSearchField: NSSearchField {
override func textDidChange(_ notification: Notification) {
Swift.print("textDidChange")
}
}
I've read the documentation, gone through their wonderful Playground example, searched S.O., and reached the extent of my google-fu, but I cannot for the life of me wrap my head around how to use ReactiveSwift.
Given the following....
class SomeModel {
var mapType: MKMapType = .standard
var selectedAnnotation: MKAnnotation?
var annotations = [MKAnnotation]()
var enableRouteButton = false
// The rest of the implementation...
}
class SomeViewController: UIViewController {
let model: SomeModel
let mapView = MKMapView(frame: .zero) // It's position is set elsewhere
#IBOutlet var routeButton: UIBarButtonItem?
init(model: SomeModel) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
// The rest of the implementation...
}
....how can I use ReactiveSwift to initialize SomeViewController with the values from SomeModel, then update SomeViewController whenever the values in SomeModel change?
I've never used reactive anything before, but everything I read leads me to believe this should be possible. It is making me crazy.
I realize there is much more to ReactiveSwift than what I'm trying to achieve in this example, but if someone could please use it to help me get started, I would greatly appreciate it. I'm hoping once I get this part, the rest will just "click".
First you'll want to use MutableProperty instead of plain types in your Model. This way, you can observe changes to them.
class Model {
let mapType = MutableProperty<MKMapType>(.standard)
let selectedAnnotation = MutableProperty<MKAnnotation?>(nil)
let annotations = MutableProperty<[MKAnnotation]>([])
let enableRouteButton = MutableProperty<Bool>(false)
}
In your ViewController, you can then bind those and observe those however necessary:
class SomeViewController: UIViewController {
let viewModel: Model
let mapView = MKMapView(frame: .zero) // It's position is set elsewhere
#IBOutlet var routeButton: UIBarButtonItem!
init(viewModel: Model) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
routeButton.reactive.isEnabled <~ viewModel.enableRouteButton
viewModel.mapType.producer.startWithValues { [weak self] mapType in
// Process new map type
}
// Rest of bindings
}
// The rest of the implementation...
}
Note that MutableProperty has both, a .signal as well as a .signalProducer.
If you immediately need the current value of a MutableProperty (e.g. for initial setup), use .signalProducer which immediately sends an event with the current value as well as any changes.
If you only need to react to future changes, use .signal which will only send events for future changes.
Reactive Cocoa 5.0 will add UIKit bindings which you can use to directly bind UI elements to your reactive layer like done with routeButton in the example.
as we know, to implement PageTabBarController, we need to insert these code in AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
}
Now, i need to use PageTabBarController when i want to open detail for my match data. My question is, how to implement it without insert those code in AppDelegate.swift because it will open my MatchViewController (extend from PageTabBarController) for the first app launch.
I have tried this code, but it will cause Crash, and it pointed to my AppDelegate.swift
class MatchViewController: PageTabBarController {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
open override func prepare() {
super.prepare()
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
//1st try: Crash
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
//2nd try: error
self.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
//3rd try: crash
self.viewControllers = viewControllers
delegate = self
preparePageTabBar()
}
fileprivate func preparePageTabBar() {
pageTabBar.lineColor = Color.blue.base
pageTabBar.dividerColor = Color.blueGrey.lighten5
pageTabBarAlignment = PageTabBarAlignment.top
pageTabBar.lineAlignment = TabBarLineAlignment.bottom
}
}
extension MatchViewController: PageTabBarControllerDelegate {
func pageTabBarController(_ pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) {
}
}
Linked GitHub Question
Hi, yes there is a way. The PageTabBarController is inherited from aUIViewController`, which allows you to add it as a child of any other UIViewController. That said, you just gave me a great idea. I am going to make a new UIViewController that allows you to add as many child UIViewControllers, which will make this super easy to do. I will make this as a Feature Request.
Until the update, please use the suggested method of adding it as a child UIViewController. Are you familiar with how to do that?
First create AppToolbarController (subclass of ToolbarController) or you can use the one in the Material library demo.
And then from your view controller, you can use:
DispatchQueue.main.async {
let tabbarViewController = AppPageTabbarController(viewControllers: [vc1,vc2,vc3], selectedIndex: 0)
self.present(AppToolbarController(rootViewontroller: tabbarViewController))
}