How to write if and else statement more elegantly in Swift - 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.

Related

iOS Swift Mapkit annotations order

I need to keep an order for the annotations. I use custom annotationViews.
Each annotations are related to a point of interest, from 1 to n, so I need to open POI 1 when the map open the first time and then, when I click next or previous, I need to open POI 2 etc..
I keep an index to show in the detail callout the Point 1. I know that annotations are a Set, not an array.
Thanks
tour.poi?.forEach {
let coord = CLLocationCoordinate2DMake(CLLocationDegrees($0.lat), CLLocationDegrees($0.long))
let poiAnnotation = PoiAnnotation(coord: coord, poi: $0, index: index)
map.addAnnotation(poiAnnotation)
index = index + 1
}
My ugly solution :
let annotation = map.annotations.first { $0.subtitle == String(poiIndex) }
UIView.animate(withDuration: 0.2) {
self.map.setCenter(annotation!.coordinate, animated: false)
}completion: { (_) in
self.map.selectAnnotation(annotation!, animated: true)
}
I've added some animation to always center the pin before opening the callout.
You said:
I know that annotations are a Set, not an array.
That is incorrect.
The map view annotations property is an array, not a set. To quote the docs:
annotations
The complete list of annotations associated with the receiver.
var annotations: [MKAnnotation] { get }
Note the type of annotations: [MKAnnotation]. That's an array.
You should be able to keep an instance variable with the index of the annotation you have displayed most recently, and increment it when you display the map again.
If you're opening and closing a map view over and over with the same set of annotations you might want to keep your array of annotations rather than rebuilding it each time. For that matter you might want to hide the map view rather than closing it, and then simply show it again. Map views use a lot of network data to display.

Whats the best way in Swift to animate an array of NSViews to appear one after another in an NSStackView?

I'm able add a number of NSTextFields to an NSStackView using the following code:
optionsStackView.addView(newOption, in: .center)
The problem I'm encountering is getting each NSTextField (newOption) to appear one after another when the view appears - not all at once when using NSAnimationContext.runAnimationGroup { (context) in }
let stackViewSubViews = optionsStackView?.arrangedSubviews.enumerated()
stackViewSubViews!.forEach({ newOption in
NSAnimationContext.runAnimationGroup { (context) in
context.duration = 1
newOption.element.animator().alphaValue = 1
}
})
I basically need to delay for a half a second each time the for loop runs to show one NSTextField after another for all the NSTextFields but I can't quite figure out how to do this. I looked at using DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) but can't quite figure out the code to make this work without everything appearing all at once...
I'm sure I'm missing something simple here.
Can anyone please help?
The simplest way to insert a delay during each iteration of a loop is to turn the loop into a recursion. I'll show you the general technique and you can adapt it to your use case:
func slowly(from i:Int, to n:Int) {
print(i)
if i != n {
DispatchQueue.main.asyncAfter(deadline:.now()+0.5) {
slowly(from:i+1, to:n)
}
}
}
slowly(from:1, to:3)

Swift: Variable values don't carry over to didSet

This is my first project in Swift so please bear with me.
punkteLimit should be initialized with value 30. The value of the variable as well as a label should be updated everytime a slider value is changed.
var punkteLimit: Int = 30
#IBAction func sliderPunktelimitChanged(_ value: Float) {
punkteLimit = Int(value)
labelPunktelimit.setText("Punkte-Limit: \(punkteLimit)")
}
This seems to work fine. The label updates correctly, i.e. when I change the slider to 28, it says "Punkte-Limit: 28". However, punkteLimit is stuck at the initial value of 30 in the following part (the same is true for considerPunktelimit but the solution should be identical). The haptic feedback will be triggered at gesamtPunkte == 30 regardless of the changes above.
I use a button that performs gesamtPunkte += 1 to adjust the value, if it matters.
var gesamtPunkte: Int = 0 {
didSet {
if gesamtPunkte == punkteLimit && considerPunktelimit == true {
WKInterfaceDevice.current().play(WKHapticType.stop)
}
...
}
}
I'm not exactly sure where to go from here.
Help is much appreciated.
I solved the problem by moving the variable(s) in question outside of the InterfaceController class (making them global?). I have read mixed opinion about this approach, if anyone wants to comment on why it didn't work inside of the class and whether or not there are problems with making them global, feel free to.

How to setup printing in cocoa, 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.)

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.