How to setup printing in cocoa, swift? - swift

I have made printing functionality for custom NSView of NSPopover by the assigning the following action to button for this NSView in mainController:
#IBOutlet var plasmidMapIBOutlet: PlasmidMapView!
#IBAction func actionPrintfMap(sender: AnyObject)
{
plasmidMapIBOutlet.print(sender)
}
It is working, but the print window has no option for Paper Size and Orientation, see screenshot below.
What should I do to get these options in the print window?
And, how to make the NSView fitting to the printable area? Now it is not fitting.
I have figured out some moments, but not completely. So, I can setup the printing by the following code
#IBAction func actionPrintMap(sender: AnyObject)
{
let printInfo = NSPrintInfo.sharedPrintInfo()
let operation: NSPrintOperation = NSPrintOperation(view: plasmidMapIBOutlet, printInfo: printInfo)
operation.printPanel.options = NSPrintPanelOptions.ShowsPaperSize
operation.printPanel.options = NSPrintPanelOptions.ShowsOrientation
operation.runOperation()
//plasmidMapIBOutlet.print(sender)
}
But, I still have problem. From the code above I can get only orientation (the last, ShowsOrientation), but not both PaperSize and Orientation. How can I manage both ShowsPaperSize and ShowsOrientation?

Finally I have found the answer which is simple to write but it is not really obvious from apple documentation.
operation.printPanel.options.insert(NSPrintPanelOptions.showsPaperSize)
operation.printPanel.options.insert(NSPrintPanelOptions.showsOrientation)

The problem in the code originally posted is that options is being assigned twice, so the first value assigned, ShowsPaperSize is overwritten by the value ShowsOrientation. That's why you only see the ShowsOrientation option in the dialog.
By using multiple insert operations, you are adding options rather than overwriting each time. You can also do it this way which I think reads better:
operation.printPanel.options.insert([.showsPaperSize, .showsOrientation])
And finally, it also works to "set" the options, and by supplying the existing options as the first array value, you achieve the affect of appending:
operation.printPanel.options = [
operation.printPanel.options,
.showsPaperSize,
.showsOrientation
]
(The first array element operation.printPanel.options means that the old options are supplied in the list of new options.)

Related

ios-charts: Can Selecting A Node Trigger A Labels Text To Change?

I am using ios-charts in swift language. I'm wanting to show more data about each node when they are selected. A marker wouldn't have enough space for all the info I am wanting to display so I am wanting to display it all in a dedicated label on the ViewController itself. Is there any way to do this?
I got the chartValueSelected method working but am not sure how to utilize it for this.
Included ChartViewDelegate
&
chtChart.delegate = self
I have an array called textNotes thats corresponds to each data entry in the array and made a label called infoLabelChart
func chartValueSelected(_ chartView: ChartViewBase, entry:
ChartDataEntry, highlight: Highlight) {
let pos = NSInteger(entry.x)
infoLabelChart.text = "\(textNotes[pos])"
}

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()
}

How to write if and else statement more elegantly in Swift

I'm having a bit of a brain fart in Swift and I know this code could be written better. Basically what it is, I have two images and I check if a value is over 3 to show an image and hide the other.
currently I have it like this
let greaterThanThree = value > 3
image1.isHidden = greaterThanThree
image2.isHidden = !greaterThanThree
But I feel like there is a more elegant way to write this.
I'd write it like this:
image1.isHidden = value > 3
image2.isHidden = !image1.isHidden
Anything shorter than that is just code golfing.
There seems to be a rule here that exactly one of these two views should be visible at all times. If so, I'd create, as part of my view controller's viewDidLoad, an instance of this struct:
struct AlternateViews {
let views : [UIView]
init(_ v1:UIView, _ v2:UIView) {
views = [v1,v2]
}
func hide(first:Bool) {
views[0].isHidden = first
views[1].isHidden = !first
}
}
let alternateViews = AlternateViews(image1, image2)
Okay, that's a lot of work to set up initially, but the result is that later you can just say
self.alternateViews.hide(first: value > 3)
The struct is acting as a tiny state machine, making sure that your view controller's views remain in a coherent state. This technique of moving the rules for state into utility structs attached to your view controller is recommended in a WWDC 2016 video and I've been making a lot of use of it ever since.
If you have more pairs of alternating views, just make and maintain more instances of the struct.
(If the rule that I've assumed is not quite the real rule, make a struct that does express the real rule.)
You can do this:
(image1.isHidden, image2.isHidden) = (value > 3) ? (true, false) : (false, true)
Basically if the value is greater than 3, the first image will be hidden and the second one won't. Otherwise, the second image will be hidden and the first one will not.

Show and hide line chart highlight on touch

I want to only highlight a data point when the finger is on the chart, as soon as it lifts off the screen I want to call, or simple deselect the highlight.
func chartValueNothingSelected(chartView: ChartViewBase) {
print("Nothing Selected")
markerView.hidden = true
}
I've tried to override the touch ended but haven't gotten it to work.
You can turn off highlighting any bars/data all together using the highlightEnabled property.
Example of this is:
barChartView.data?.highlightEnabled = false
If you still want to be able to highlight values, but want them to automatically deselect after the touch has ended, I also found another function highlightValues(highs: [ChartHighlight]?) which says in the documentation..
Provide null or an empty array to undo all highlighting.
Call this when you want to deselect all the values and I believe this will work. Example of this could be:
let emptyVals = [ChartHighlight]()
barChartView.highlightValues(emptyVals)
Ref:
Charts Docs: highlightValues documentation
If you don't have to do anything with the tapped data you can use:
barChartView.data?.highlightEnabled = false
If you want to use the tapped data point without displaying the highlight lines, you can use the selection delegate (don't forget to add ChartViewDelegate to your class):
yourChartView.delegate = self // setup the delegate
Add delegate function:
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
// do something with the selected data entry here
yourChartView.highlightValue(nil) // deselect selected data point
}

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.