Eureka PushRow on a modal, view won't close after item selection - swift

I am using a SplitViewController and on the detail page (which is set to "defines context"), the user can select "+" in the navbar and I present the next view controller "modally over current context" using a segue. On that view controller I am using Eureka and one of the rows I want to use is PushRow. The issue I am running into is when I select an option on the PushRow, the view (table of options to choose that Eureka generated) never closes. The list of options stays full screen. I can see that PushRow.onChange is called and it has the correct value. For some reason that topmost view will not close.
I dug deeper and it seems like I need to modify the PushRow presentationMode to be "presentModally" since I am presenting it from a modal. However, I am not sure what to put for the controllerProvider. Is this the right path? If so, what would the correct syntax be? I also tried doing a reload in the onChange but that didn't make a difference.
private func getGroupPushRow() -> PushRow<String> {
return PushRow<String>() {
$0.title = "Group"
$0.selectorTitle = "What is the Group?"
$0.noValueDisplayText = "Select a Group..."
$0.options = self.getGroups()
$0.presentationMode = PresentationMode.presentModally(controllerProvider: ControllerProvider<VCType>, onDismiss: { () in
})
$0.onChange({ (row) in
print("in onchange \(row.value)")
// row.reload()
// self.tableView.reloadData()
})
}
}

I eventually figured out a solution so I'm posting it here to hopefully help out somebody else. Going off of the example above, replace presentationMode & onChange with this code. Note that if you are using another object in your PushRow besides String, then the type in PushSelectorCell should be that type instead.
$0.presentationMode = PresentationMode.presentModally(
controllerProvider: ControllerProvider.callback {
return SelectorViewController<SelectorRow<PushSelectorCell<String>>> { _ in }
},
onDismiss: { vc in
vc.dismiss(animated: true)
})
$0.onChange({ (row) in
row.reload()
})

Related

Is there a way to pass functions into views as optional parameters?

I am making an application in SwiftUI that involves answering yes or no questions. Because of this I have created a subview call YesOrNoView. This subview has two buttons, one labeled yes and the other labeled no. The view accepts a binding variable of question that returns true or false based on the users answer. It also accepts a binding variable of questionsAnswered and increments it whenever either button is pressed. This behavior works for most questions in the game, but for the final question I want it to execute custom logic to take the user to a different view. To do this I am trying to make the view accept a custom action/method that I can call from either buttons action logic. Ideally, the action would also be optional so that I don't have to pass it in the 99% percent of the time when I'm not using it.
How do I pass a function as an optional parameter into a view and then run that action when a button within said view is pressed?
I tried adding actions using
struct YesOrNoView<Content: Action>: View {
...
but it couldn't find action or Action within it's scope.
I also tried using
struct YesOrNoView<Content>: View {
But I got an error saying that the Content variables type could not be inferred.
Any help would be greatly appreciated.
It return clickAction back to the view
struct GenericButton<Title: StringProtocol>: View {
let title: Title
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
}.frame(width: 200, height: 40)
}
}
}
Usage:
GenericButton("Button") {
// Button tapped action
}
I managed to figure it out with help from #JoakimDanielson. The trick is to pass in an empty function as the default.
var myFunction: () -> Void = {}
Thanks for the help everyone.

SwiftUI: How to Disable List Scrolling on Reusable View

I've got an interesting glitch in SwiftUI -
I'm using a List as part of my interface that is intended to be un-scrollable. This is how I've designed it:
List {
ForEach(myArr) {
...
}
}
.listStyle(.plain)
.onAppear {
UIScrollView.appearance().isScrollEnabled = false
UIScrollView.appearance().showsVerticalScrollIndicator = false
}
Now, when I first load the view containing this list, it works perfectly; you cannot scroll this list. However, if I navigate to a different page in my application and then back to the page containing this list (which creates a new instance of the view), scrolling becomes enabled.
How can I fix this?
Use .disabled(true) on the list to disable interaction with the list.

Looping through entities which describe what action should be taken on screen after keypresses

Please forgive me if I don't describe this question too well, I am new to programming MacOS apps using Swift. I know the way I'm going about this is probably wrong and I just need someone to tell me the right way.
My main app screen
I have a Core Data application that stores an ordered list of entities called Items. These Items are intended to describe a single step in an activity that describes what should happen on screen. If you know the Mac application QLab each Item is like a single cue in QLab.
I have created an Activity class that is designed to read through each Item to determine the Item type and it's related information. Once the Item type has been determined the Activity class needs to present a View with information related to that particular Item and then wait until the user presses the right arrow key to then proceed to the next Item in the Core Data store where the process repeats until all Items have been read. Each time a new Item is read in the loop, the information on the screen should change after the user presses the right arrow each time.
The problem is that I don't know exactly how the best way of going about this should be programatically speaking. I have the code that retrieves the array of Items as an NSFetchRequest:
let moc = (NSApplication.shared.mainWindow?.contentViewController?.representedObject as! NSPersistentDocument).managedObjectContext!
let fetchRequest : NSFetchRequest = Item.fetchRequest()
do {
let items = try moc.fetch(fetchRequest)
print("Found " + String(items.count) + " items to use in the activity.")
for item in items {
print(item.itemType)
// How do I pause this loop for a user keypress after using data from this Item to display?
}
} catch {
print("Error retrieving Items")
}
I can retrieve the keydown event using NSEvent.addLocalMonitorForEvents(matching: .keyDown) and I'm also able to create View Controllers to display the information on a second screen. I just don't know how I should create the 'main loop', so to speak, so that information is displayed and then the app waits until the user presses a key to proceed...
I can share my project code if more information is needed and many thanks to anyone who can enlighten me... :)
You could try using a NSPageController. In your NSPageController you add a ContainerView which will display the ViewControllers that display information for each item. Each ViewController will need a storyboard identifier, e.g. ViewControllerItem1.
Your NSPageController class must conform to the NSPageControllerDelegate protocol and contains an array of ViewControllers to display.
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
arrangedObjects = ["ViewControllerItem1", "ViewControllerItem2", "...","ViewControllerItemN" ]
}
Note about arrangedObjects from the NSPageController documentation: An array containing the objects displayed in the page controller’s view.
Then you implement NSPageControllers viewControllerForIdentifier to return the ViewController that you currently want to display in the ContainerView.
func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
switch identifier {
case "ViewControllerItem1":
return mainStoryboard().instantiateController(withIdentifier:"ViewControllerItem1") as? ViewControllerItem1
case "...":
default:
}
}
In your action handler for the key down event you implement.
self.navigateForward(sender) or self.navigateBack(sender)
I also implemented this method but I don't remember whether it was required.
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
self.completeTransition()
}

Creating 1Password-like macOS menubar app

I am trying to create a macOS menubar app which will have a text field as the first item. The textfield will serve as a search bar for filtering other items which will be displayed below it.
It should look very similar to 1password:
This is what I managed to do:
I have accomplished this by creating a Status menu with three items and creating a custom view for the first item in the menu.
However, this approach does not seem to solve my issue. When pressing cmd + A in the search field, the focus jumps to the next item in the menu. This is the default behaviour for NSMenu.
So, my question is: Is this the right approach to create a 1Password-like app or is there a better one?
Basically the approach is correct.
But you have to catch the edit key events explicitly. Subclass NSTextField and override performKeyEquivalent
class AXCVTextField: NSTextField {
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if event.modifierFlags.contains(.command),
let key = event.charactersIgnoringModifiers {
var action : String?
switch key {
case "x": action = "cut:"
case "c": action = "copy:"
case "v": action = "paste:"
case "a": action = "selectAll:"
default:
break
}
if let action = action {
return NSApp.sendAction(Selector(action), to:self.window!.firstResponder, from:self)
}
}
return super.performKeyEquivalent(with: event)
}
}

How to have a subject in RxSwift push values to itself without creating an infinite loop

I have a UITableView, which I want to put into an editing state if certain conditions are met. The primary way to toggling edit is through an edit button.
So the view elements I have are
let tableView = UITableView()
let editButton = UIButton()
And whether the tableView should be in editing mode is fed from:
let editing = BehaviorSubject(value: false)
Which will be hooked up to the tableView using something like:
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
When the edit button is tapped, I want that to push a new value to editing, that is the negation of the most recent value sent to editing. The most recently value may have been set by a tap on editButton, or it may have come from somewhere else.
I don't understand how to combine the stream for the button press with the stream for editing in such a way that allows this without an infinite loop e.g.
Obervable.combineLatest(editButton.rx_tap.asObservable(), editing) { _, isEditing in
editing.onNext(!isEditing)
}
I'm aware that the tableView has an editing property, but I don't want to rely on that as I am looking for a more general solution that I can re-use elsewhere. I'm also not looking to track the value of isEditing in an instance var, or even as a Variable(), as I am looking for a stateless, stream based solution (if this is at all possible).
Thank you!
With some help from the RxSwift GitHub issues forum I've now worked it out :). The key was withLatestFrom. I've included an example of this below in case it will help anyone else. editButton is the primary way to trigger editing mode on or off, and I've included an event sent via tableView.rx_itemSelected as an additional input example (in this case, I want editing to end any time an item is selected).
let isEditing = BehaviorSubject(value: false)
let tableView = UITableView()
let editButton = UIButton()
tableView.rx_itemSelected
.map { _ in false }
.bindTo(isEditing)
editButton.rx_tap.withLatestFrom(isEditing)
.map { !$0 }
.bindTo(isEditing)
isEditing.subscribeNext { editing in
tableView.setEditing(editing, animated: true)
}
Note: This solution sends .Next(false) to isEditing every time an item is selected, even if the table isn't currently in editing mode. If you feel this is a bad thing, and want to filter rx_itemSelected to only send .Next(false) if the table is actually in editing mode, you could probably do this using a combination of withLatestFrom and filter.
What if you define editing as a Variable instead of a BehaviourSubject. A Variable cannot error out which makes sense in this case. The declaration would look like this:
let editing = Variable(value: false)
You could subscribe to a button tap and change the value of editing to the negated current one:
editButton.rx_tap.asObservable().subscribeNext { editing.value = !editing.value }
With changing the value property of editing this method is called
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
All of this is not tested, but might lead you in the right direction for the right solution.