make dynamic UIbutton appear and dissapear based on number of items in table view - swift

I'm currently trying to make a dynamic UIbutton appear and disappear based on number of items loaded into a table view, fetched from a backend url. I want to button to appear if there's 12 or more items loaded into the table view and not appear if there's less than 12. Any ideas on the best way to handle this?
import UIKit
import RxSwift
import RxCocoa
public class AllProvidersPickerViewController: InputableTableViewController, ViewModelHolder {
#IBOutlet private(set) weak var searchBar: UISearchBar!
#IBOutlet weak var dontSeeProviderButton: UIButton!
var viewModel: AllProvidersPickerViewModel! = nil
private let bag = DisposeBag()
override public func viewDidLoad() {
super.viewDidLoad()
setupRx()
}
private func setupRx() {
viewModel.shownProviders
.bind(to: tableView.rx.items(cellIdentifier: "ProviderCell")) { _, mvpd, cell in
cell.textLabel?.text = mvpd.displayName
}
.addDisposableTo(bag)
tableView
.rx
.modelSelected(MVPD.self)
.bind(to: viewModel.selectedProvider)
.addDisposableTo(bag)
searchBar
.rx.text
.orEmpty
.bind(to: viewModel.searchQuery)
.addDisposableTo(bag)
dontSeeProviderButton
.rx.tap
.bind(to: viewModel.tappedDontSeeProvider)
.addDisposableTo(bag)
}
}
private extension MVPD {
var displayName: String {
return self.names.first ?? ""
}
}

XFreire's answers are fine, or you could do:
viewModel.shownProviders
.map { $0.count < 12 }
.bind(to: dontSeeProviderButton.rx.isHidden)
.disposed(by: bag)
Make sure shownProviders can handle being subscribed to without having to re-send any network requests or whatever. You might need shareReplayLatestWhileConnected() for that.
I have been asked to explain this code... I will do so by breaking it down...
let shownProviders = viewModel.shownProviders
At this point, I know that shownProviders is an array. I don't know much about the type that the array contains because that info wasn't in the question, but I don't need to know
let shownProviders = viewModel.shownProviders
let shouldHideButton = shownProviders.map { $0.count < 12 }
In the above line, I know that $0 is an array and I know that the button should hide if there are fewer than 12 items in the array. $0.count < 12 returns a Bool. map will transform the shownProviders Observable into whatever the block returns, so I know that shouldHideButton is an Observable<Bool>.
let shownProviders = viewModel.shownProviders
let shouldHideButton = shownProviders.map { $0.count < 12 }
let disposable = shouldHideButton.bind(to: dontSeeProviderButton.rx.isHidden)
The above line of code binds the result of shouldHideButton to the isHidden property of the button. It returns a disposable.
let shownProviders = viewModel.shownProviders
let shouldHideButton = shownProviders.map { $0.count < 12 }
let disposable = shouldHideButton.bind(to: dontSeeProviderButton.rx.isHidden)
disposable.disposed(by: bag)
This last line ensures that the binding will be broken when the view controller goes out of scope.

Simplest way:
viewModel.shownProviders
.subscribe(onNext: { [weak self] items in
if items.count < 12 {
self?.viewAllProvidersButton.isHidden = true
}
else {
self?.viewAllProvidersButton.isHidden = false
}
})
.addDisposableTo(bag)
Other way could be to create a property buttonVisibilityObserver of type AnyObserver and bind it to viewModel.shownProviders. Something like this (not tested):
var buttonVisibilityObserver: AnyObserver<[ItemsType]> {
return UIBindingObserver(UIElement: viewAllProvidersButton) { button, items in
button.isHidden = items.count < 12 ? true : false
}.asObserver()
}
And then in your setupRx():
viewModel.shownProviders
.bind(to: buttonVisibilityObserver)
.addDisposableTo(bag)

Related

How to pass chain view controller presenter with observable

I'm new in the RxSwift development and I've an issue while presentation a view controller.
My MainViewController is just a table view and I would like to present detail when I tap on a item of the list.
My DetailViewController is modally presented and needs a ViewModel as input parameter.
I would like to avoid to dismiss the DetailViewController, I think that the responsability of dismiss belongs to the one who presented the view controller, i.e the dismiss should happen in the MainViewController.
Here is my current code
DetailsViewController
class DetailsViewController: UIViewController {
#IBOutlet weak private var doneButton: Button!
#IBOutlet weak private var label: Label!
let viewModel: DetailsViewModel
private let bag = DisposeBag()
var onComplete: Driver<Void> {
doneButton.rx.tap.take(1).asDriver(onErrorJustReturn: ())
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func bind() {
let ouput = viewModel.bind()
ouput.id.drive(idLabel.rx.text)
.disposed(by: bag)
}
}
DetailsViewModel
class DetailsViewModel {
struct Output {
let id: Driver<String>
}
let item: Observable<Item>
init(with vehicle: Observable<Item>) {
self.item = item
}
func bind() -> Output {
let id = item
.map { $0.id }
.asDriver(onErrorJustReturn: "Unknown")
return Output(id: id)
}
}
MainViewController
class MainViewController: UIViewController {
#IBOutlet weak private var tableView: TableView!
private var bag = DisposeBag()
private let viewModel: MainViewModel
private var detailsViewController: DetailsViewController?
override func viewDidLoad(_ animated: Bool) {
super.viewDidLoad(animated)
bind()
}
private func bind() {
let input = MainViewModel.Input(
selectedItem: tableView.rx.modelSelected(Item.self).asObservable()
)
let output = viewModel.bind(input: input)
showItem(output.selectedItem)
}
private func showItem(_ item: Observable<Item>) {
let viewModel = DetailsViewModel(with: vehicle)
detailsViewController = DetailsController(with: viewModel)
item.flatMapFirst { [weak self] item -> Observable<Void> in
guard let self = self,
let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
self.present(detailsViewController, animated: true)
return detailsViewController.onComplete.asObservable()
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController? = nil
})
.disposed(by: bag)
}
}
MainViewModel
class MainViewModel {
struct Input {
let selectedItem: Observable<Item>
}
struct Output {
let selectedItem: Observable<Item>
}
func bind(input: Input) -> Output {
let selectedItem = input.selectedItem
.throttle(.milliseconds(500),
latest: false,
scheduler: MainScheduler.instance)
.asObservable()
return Output(selectedItem: selectedItem)
}
}
My issue is on showItem of MainViewController.
I still to think that having the DetailsViewController input as an Observable isn't working but from what I understand from Rx, we should use Observable as much as possible.
Having Item instead of Observable<Item> as input could let me use this kind of code:
item.flatMapFirst { item -> Observable<Void> in
guard let self = self else {
return Observable<Void>.never()
}
let viewModel = DetailsViewModel(with: item)
self.detailsViewController = DetailsViewController(with: viewModel)
guard let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
present(detailsViewController, animated: true)
return detailsViewController
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController = nil
})
.disposed(by: bag)
What is the right way to do this?
Thanks
You should not "use Observable as much as possible." If an object is only going to ever have to deal with a single item, then just pass the item. For example if a label is only ever going to display "Hello World" then just assign the string to the label's text property. Don't bother wrapping it in a just and binding it to the label's rx.text.
Your second option is much closer to what you should have. It's a fine idea.
You might find my CLE library interesting. It takes care of the issue you are trying to handle here.

RXSwift collectionView doesn't update when calling .onNext(newArray)

I got a problem. I got a collectionview which is binded to a winPinataActions PublishSubject<[Object]>(). Initially, when loading collectionview everything is fine, it displays as it has to the objects, however when the pull to refresh action changes the publishSubject data the UI is not updated, it still gets the old content of the PublishSubject.
Here is how I bind the collectionView :
class WinPinatasViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
}
func configureCollectionView() {
/..../
viewModel.winPinataActions
.observeOn(MainScheduler.instance)
.bind(to: collectionView.rx.items(cellIdentifier: "winPinatasCell", cellType: WinPinatasCell.self)) {(row, item, cell) in
cell.configureCell(with: item)
}.disposed(by: bag)
viewModel.getPinataActions()
}
#objc func handleRefreshControl() {
viewModel.getPinataActions()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.collectionView.refreshControl?.endRefreshing()
}
}
}
This is my viewModel class:
class WinPinatasViewModel {
let winPinataActions = PublishSubject<[WinPinatasAction]>()
func getPinataActions() {
guard let ssoId = UserDefaultsStore.ssoId() else {
return
}
NetworkEngine.shared.gamificationNetwork.getUserWinPinataActions(subject: winPinataActions, ssoID: ssoId)
}
}
And my NetworkEngine getuserPinataActions method:
func getUserWinPinataActions(subject winPinatasActions: PublishSubject<[WinPinatasAction]>, ssoID: String) {
//...//
let actions = try decoder.decode([WinPinatasAction].self, from: jsonData)
winPinatasActions.onNext(actions)
winPinatasActions.onCompleted()
//...//
}
When the pull to refresh action is done, the handleRefreshControl() method is called. Also While debugging I could see that after pullToRefresh action the new data is received inside my NetworkEngine method and both .onNext()and onCompleted() are called. But when I scroll through the collectionView the data the cell items are from the old array, not the one new one. Could you help me please? What am I doing wrong?
The problem here is that you are sending a completed event to the Subject but then expecting it to be able to send other events after that. The Observable contract specifies that once an Observable (or Subject in this case) sends a completed event, it will never send any more events under any circumstances.
Instead of passing a Subject into getUserWinPinataActions you should be returning an Observable from the function.
This is closer to what you should have:
class WinPinatasViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let bag = DisposeBag()
let viewModel = WinPinatasViewModel()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.refreshControl!.rx.controlEvent(.valueChanged)
.startWith(())
.flatMapLatest { [viewModel] in
viewModel.getPinataActions()
}
.observeOn(MainScheduler.instance)
.bind(to: collectionView.rx.items(cellIdentifier: "winPinatasCell", cellType: WinPinatasCell.self)) {(row, item, cell) in
cell.configureCell(with: item)
}
.disposed(by: bag)
}
}
class WinPinatasViewModel {
func getPinataActions() -> Observable<[WinPinatasAction]> {
guard let ssoId = UserDefaultsStore.ssoId() else {
return .empty()
}
return GamificationNetwork.shared.getUserWinPinataActions(ssoID: ssoId)
}
}
class GamificationNetwork {
static let shared = GamificationNetwork()
func getUserWinPinataActions(ssoID: String) -> Observable<[WinPinatasAction]> {
Observable.create { observer in
let jsonData = Data() // get jsonData somehow
let actions = try! decoder.decode([WinPinatasAction].self, from: jsonData)
observer.onNext(actions)
observer.onCompleted()
return Disposables.create { /* cancelation code, if any */ }
}
}
}
Remember:
Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use... In production code you may find that you rarely use the IObserver interface and subject types... The IObservable interface is the dominant type that you will be exposed to for representing a sequence of data in motion, and therefore will comprise the core concern for most of your work with Rx...
-- Intro to Rx
If you find yourself reaching for a Subject to solve a problem, you are probably doing something wrong.
Also, this article might help: Integrating RxSwift Into Your Brain and Code Base

RxSwift Observe changes on model and Make request

I'm trying to learn RxSwift concept and got stuck somewhere unfortunately. There is two different screen connected to my TabBarController. On my SettingsViewController, I'm getting two string values and creating a model, On TransactionListViewController, I need to observe changes on and make a new request to fill list.
On parent tab bar controller, I have a Variable and when didLoadCall I'm subscribing this model with wallet.asObservable().subscribe
On SettingViewController when user presses the login button I'm trying to change UserModel with this code:
if let tabBar = parent?.parent as? TransactionTabBarController{
Observable.just(wallet).bind(to: tabBar.wallet)
}
I realized that onNext function for wallet.asObservable().subscribe is calling.
There is also another wallet model on my TransactionListViewController,
on viewDidLoad function I'm running this code:
wallet.asObservable().subscribe(onNext: { (wallet) in
APIClient.getTransaction(address: wallet.walletAddress)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (model) in
self.changeModels(items: model.result)
.bind(to: self.transactionTableView.rx.items(dataSource: self.dataSource))
.disposed(by: self.disposeBag)
})
.disposed(by: self.disposeBag)}, onError: nil, onCompleted: nil, onDisposed: nil)
.disposed(by: disposeBag)
I tried to set wallet on TabBar's onNext function and I got crush couple of times on TransactionListViewController.
Can anyone help me with that?
Sadly, your code sample is inscrutable. However, it seems as though you are asking how to transmit data between two view controllers that are connected through a tab bar view controller. Below is one way you could go about doing it...
In order to use this code, you only need to assign a function to TabBarController.logic which takes a TabBarController.Inputs as an input parameter and returns a TabBarController.Outputs. You could make this assignment in the AppDelegate.
The key thing to note in this code is that every ViewController subclass has a struct Inputs, a struct Outputs and a var logic in it.
The Inputs has all the UI elements that a user can input to (e.g., Buttons and TextFields,) and the Outputs has all the UI elements that the user can see (e.g., Label text, isHidden flags.)
The logic var is a closure that contains all the logic for that view controller. Note that it can be assigned to. That means that you can develop and test the logic independently of the view controller and you can provide a view controller with a different logic object if necessary depending on context.
For somewhat more complex example code that uses a Coordinator instead of embedding code in the container view controller, see this repo: https://github.com/danielt1263/RxEarthquake
class TabBarController: UITabBarController {
struct Inputs {
let login: Observable<Void>
}
struct Outputs {
let transactions: Observable<[Transaction]>
}
var logic: (Inputs) -> Outputs = { _ in fatalError("Forgot to set logic.") }
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let settings = children[0] as! SettingsViewController
let transactionList = children[1] as! TransactionListViewController
let login = PublishSubject<Void>()
let outputs = logic(Inputs(login: login.asObservable()))
let bag = self.bag
settings.logic = { inputs in
inputs.login
.bind(to: login)
.disposed(by: bag)
return SettingsViewController.Outputs()
}
transactionList.logic = { inputs in
return TransactionListViewController.Outputs(transactions: outputs.transactions)
}
}
}
class SettingsViewController: UIViewController {
struct Inputs {
let login: Observable<Void>
}
struct Outputs {
}
var logic: (Inputs) -> Outputs = { _ in fatalError("Forgot to set logic.") }
private let bag = DisposeBag()
#IBOutlet weak var login: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
_ = logic(Inputs(login: login.rx.tap.asObservable()))
}
}
class TransactionListViewController: UIViewController {
struct Inputs {
}
struct Outputs {
let transactions: Observable<[Transaction]>
}
var logic: (Inputs) -> Outputs = { _ in fatalError("Forgot to set logic.") }
private let bag = DisposeBag()
#IBOutlet weak var transactionTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let output = logic(Inputs())
let dataSource = MyDataSource()
output.transactions
.bind(to: transactionTableView.rx.items(dataSource: dataSource))
.disposed(by: bag)
}
}

UISearchBar in UITableView by Rxswift

I use RxSwift to show list of Persons in my tableview, and my tableview has two sections, the first one is old searches and the second one is all Persons. now I don't know how should I filter Persons when users type a name on UISearchBar's textfield.
This is my Person model:
struct PersonModel {
let name: String
let family:String
let isHistory:Bool
}
This is my ContactsViewModel
struct SectionOfPersons {
var header: String
var items: [Item]
}
extension SectionOfPersons: SectionModelType {
typealias Item = PersonModel
init(original: SectionOfPersons, items: [SectionOfPersons.Item]) {
self = original
self.items = items
}
}
class ContactsViewModel {
let items = PublishSubject<[SectionOfPersons]>()
func fetchData(){
var subItems : [SectionOfPersons] = []
subItems.append( SectionOfPersons(header: "History", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:true ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:true )
]))
subItems.append( SectionOfPersons(header: "All", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:false ),
SectionOfPersons.Item(name:"James", family:"Patterson", isHistory:false ),
SectionOfPersons.Item(name:"Stephen", family:"King", isHistory:false ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:false )
]))
self.items.onNext( subItems )
}
}
and this is my ContactsViewController:
class ContactsViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionOfPersons>(configureCell: configureCell, titleForHeaderInSection: titleForHeaderInSection)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SectionOfPersons>.ConfigureCell = { [weak self] (dataSource, tableView, indexPath, contact) in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath) as? ContactTableViewCell else { return UITableViewCell() }
cell.contact = contact
return cell
}
private lazy var titleForHeaderInSection: RxTableViewSectionedReloadDataSource<SectionOfPersons>.TitleForHeaderInSection = { [weak self] (dataSource, indexPath) in
return dataSource.sectionModels[indexPath].header
}
private let viewModel = ContactsViewModel()
private let disposeBag = DisposeBag()
var showContacts = PublishSubject<[SectionOfPersons]>()
var allContacts = PublishSubject<[SectionOfPersons]>()
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
viewModel.fetchData()
}
func bindViewModel(){
tableView.backgroundColor = .clear
tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactTableViewCell")
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.items.bind(to: allContacts).disposed(by: disposeBag)
viewModel.items.bind(to: showContacts).disposed(by: disposeBag)
showContacts.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
searchBar
.rx.text
.orEmpty
.debounce(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.filter { !$0.isEmpty }
.subscribe(onNext: { [unowned self] query in
////// if my datasource was simple string I cand do this
self.showContacts = self.allContacts.filter { $0.first?.hasPrefix(query) } // if datasource was simple array string, but what about complex custome object?!
})
.addDisposableTo(disposeBag)
}
}
Thanks for your response.
You don't need the two PublishSubjects in your ContactsViewController. You can bind the Observables you obtain from the UISearchBar and your viewModel directly to your UITableView. To filter the contacts with your query you have to filter each section separately. I used a little helper function for that.
So here is what I did
Get rid of the showContacts and allContacts properties
Create an query Observable that emits the text that the user entered into the search bar (don't filter out the empty text, we need that to bring back all contacts when the user deletes the text in the search bar)
Combine the query Observable and the viewModel.items Observable into one Observable
Use this observable to filter all contacts with the query.
Bind that Observable directly to the table view rx.items
I used combineLatest so the table view gets updated whenever the query or viewModel.items changes (I don't know if that list of all contacts is static or if you add / remove contacts).
So now your bindViewModel() code looks like this (I moved the tableView.register(...) to viewDidLoad):
func bindViewModel(){
let query = searchBar.rx.text
.orEmpty
.distinctUntilChanged()
Observable.combineLatest(viewModel.items, query) { [unowned self] (allContacts, query) -> [SectionOfPersons] in
return self.filteredContacts(with: allContacts, query: query)
}
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
Here is the function that filters all contacts using the query:
func filteredContacts(with allContacts: [SectionOfPersons], query: String) -> [SectionOfPersons] {
guard !query.isEmpty else { return allContacts }
var filteredContacts: [SectionOfPersons] = []
for section in allContacts {
let filteredItems = section.items.filter { $0.name.hasPrefix(query) || $0.family.hasPrefix(query) }
if !filteredItems.isEmpty {
filteredContacts.append(SectionOfPersons(header: section.header, items: filteredItems))
}
}
return filteredContacts
}
I assumed that you wanted to check the Persons' name and family against the query.
One more thing: I removed the debounce because you filter a list that is already in memory, which is really fast. You would typically use debounce when typing into the search bar triggers a network request.

Reload a NSWindow Xcode Swift2

I'm working on an NSOutlineView that uses NSView subclasses to generate custom cells in the outline. This I've gotten to work, BUT after the Outline sucks in the data from the model class and displays it correctly, the Outline is released(?) from memory / goes to nil and I haven't figured out a way to get it back.
Here is the MainViewController class
class MainWindowController: NSWindowController, ShareInfoDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource {
override var windowNibName: String {
return "MainWindowController"
}
#IBOutlet var daOutline: NSOutlineView!
// The NSoutline I'm trying to get back to
Some stuff related to the test data (Omitted)
leading us to the NSOutlineViewDataSource stuff
//MARK: - NSOutlineViewDataSource
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements[index]
case let movement as Movement:
return movement.tracks[index]
default:
let track = item as! Track
return track.credits[index]
}
} else {
if allWorks.count > 0 {
return allWorks[index]
}
}
let q = "patience"
return q
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
switch item {
case let work as Work:
return (work.movements.count > 0) ? true : false
case let movement as Movement:
return (movement.tracks.count > 0) ? true : false
case let track as Track:
return (track.credits.count > 0) ? true: false
default:
return false
}
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements.count
case let movement as Movement:
return movement.tracks.count
case let track as Track:
return track.credits.count
default:
return 0
}
} else {
return allWorks.count
}
}
func outlineView(daOutline: NSOutlineView, viewForTableColumn theColumn: NSTableColumn?, item: AnyObject) -> NSView? {
switch item {
case let worked as Work:
let cell = daOutline.makeViewWithIdentifier("newTry", owner:self) as! newTry
cell.fourthLabel.stringValue = worked.composer
cell.fourthCell.stringValue = worked.title
return cell
case let moved as Movement:
let cell2 = daOutline.makeViewWithIdentifier("SecondTry", owner:self) as! SecondTry
cell2.roman.stringValue = moved.name!
cell2.details.stringValue = moved.sections!
cell2.track.stringValue = "0"
return cell2
default:
print("probably not")
}
print("not again")
return nil
}
func outlineView(daOutline: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
switch item {
case let worked as Work:
return 40
default:
return 24
}
}
And the stuff in WindowDidLoad
override func windowDidLoad() {
super.windowDidLoad()
let nib = NSNib(nibNamed: "newTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib!, forIdentifier: "newTry")
let nib2 = NSNib(nibNamed: "SecondTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib2!, forIdentifier: "SecondTry")
//give Sender it's Receiver
mailItOut.delegate = receiver
allWorks.append(work1)
allWorks.append(work2)
work1.movements.append(move1)
work1.movements.append(move2)
work1.movements.append(move3)
work1.movements.append(move4)
work2.movements.append(move5)
work2.movements.append(move6)
work2.movements.append(move7)
daOutline.reloadData()
daOutline?.expandItem(work1, expandChildren: false)
daOutline?.expandItem(work2, expandChildren: false)
}
}
And Finally what the newTry NSView class looks like
class newTry: NSView {
var delegate: ShareInfoDelegate?
#IBOutlet weak var fourthCell: NSTextField!
#IBOutlet weak var fourthLabel: NSTextField!
#IBAction func cellAdd(sender: NSTextField) {
var catchIt: String = String()
catchIt = sender.stringValue
if catchIt != "" {
tryAgain = catchIt
whichField = "title"
//Trigger the sender to send message to it's Receiver
mailItOut.sendMessage()
}
}
The cellAdd Action is used to try and get user input from the text cells back into the model. To do this I (AFAIK) need to access the NSOutline (daOutline) and get which row I'm at and put the data from the sender into the appropriate part of the Model class. Which is something that I've managed to get to work in a standard (1 cell / 1 data value) outline. But in this prototype, as far as I can tell, the MainWindowController has released all of its contents and daOutline is nil (bad).
How do I get XCode to bring / reload the completed outline (or never release it) and get daOutline to a non nil state?
For those who come after there appeared to be two problems that led to the NSOutline outlet becoming nil. The first one was that in implementing the delegate protocol "shareInfoDelegate" I was creating a new instance of the MainWindowController, not the one with the data in it. This new instance did NOT have the IBOutlets connected (or much of anything useful about it).
Once I scrapped the Delegate and moved to using NSNotification to update information about the NSView textFields my NSOutline came "back".
The second, more minor, problem was that in the NSView nib file I placed and NSBox to mimic the behavior of a group row (e.g. a gray background). As a side effect the NSBox was inhibiting the normal row select behavior of the outline. Which made it very hard to determine which row was selected. When I deleted the NSBox, row selection became much more easy to determine.
in particular this Question and the answer by Chuck were helpful in sniffing this out.
Why is my NSOutlineView datasource nil?
Thanks Indeed(!)