Can you both find a first instance of a type in a collection, returning that instance as that concrete type? - swift

This code bothers me. Below, I'm trying to find the first instance of a specific type of ViewController in a NavigationController's stack. Simple. But when I've found it, I have to then cast it to the type I just looked for, which seems redundant to me.
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.first(where: { $0 is T }) as? T else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
Only thing I can think of is this...
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.flatMap({ $0 as? T }).first() else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
...but I've repeatedly found using flatMap like this tends to confuse people reading the code, and, as correctly pointed out in the comments below, iterates over the entire collection whereas first doesn't do that.
So is there another way to solve this issue?

You can use case patterns to select the viewControllers of the type you are interested in and pop and return the first one you find:
extension UINavigationController {
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
for case let vc as T in viewControllers {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}
}
Example:
Use a button in OrangeViewController to return to GreenViewController earlier in the stack:
#IBAction func popToGreen(_ sender: UIButton) {
let greenVC = self.navigationController?.popToFirstViewController(
ofType: GreenViewController.self,
animated: true
)
// Modify a property in GreenViewController that
// will be moved into a label in viewWillAppear
greenVC?.labelText = "Returned here from Orange"
}
popToLastViewController(ofType:animated:)
You might also want a function to pop to the most recent viewController of a type. That is easily achieved with a simple modification (adding .reversed()):
func popToLastViewController<T:UIViewController>(ofType type:T.Type, animated: Bool) -> T? {
for case let vc as T in viewControllers.reversed() {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}

I'm in favor of combining flatMap and lazy to get the behavior of conditionally casting to T, stripping out mismatches, and not enumerating the whole array:
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.lazy.flatMap({ $0 as? T }).first {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
As for "confusing people that read the code:" flatMap is fairly idiomatic Swift, and will be less ambiguous with the upcoming rename to compactMap. If readers in your environment really have trouble, you could always write a small helper (generic or not) that performs the same work under a clearer name.

Related

How use AXObserverAddNotification?

I want to trigger a function when a Window focus is changed
How can I add an observer based on kAXFocusedWindowChangedNotification
I am trying to use like this:
override func viewDidLoad() {
super.viewDidLoad()
var observer: AXObserver? = nil
let pid_app: pid_t = NSWorkspace.shared.runningApplications.first(where: {$0.localizedName == "Safari"})!.processIdentifier as pid_t
let app_ref = AXUIElementCreateApplication(pid_app)
func callBack(observer: AXObserver?, element: AXUIElement?, notification: CFString, refcon: UnsafeMutableRawPointer?) {print("Fired!")}
if AXObserverCreate(pid_app, callBack, &observer) == .success {
print("AXObserverCreate success!")
if AXObserverAddNotification(observer!, app_ref, kAXFocusedWindowChangedNotification as CFString, UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque())) == .success {
print("AXObserverAddNotification success!")
CFRunLoopAddSource(CFRunLoopGetCurrent(), AXObserverGetRunLoopSource(observer!), .defaultMode)
print("Watching")
}
}
}
so, why the func callback is not has being triggered when I change window focus of safari?
As #Willeke has already pointed out, you need to keep a reference to observer (probably you want to make it a property).
For others who are interested, also take a look at this answer here https://stackoverflow.com/a/33262376/1531256 explaining how to pass self to the callback C-function.

Warnings from String interpolation

I've encountered a problem I can't solve myself. I have tried the Internet without any luck.
I'm still pretty new to Swift and coding, and right now following a guide helping me create an app.
Unfortunately, as I can understand, the app was written for Swift 3, and is giving me some issues since I'm using Swift 4.
I have to lines that gives me this warning:
String interpolation produces a debug description for an optional value; did you mean to make this explicit?
Use 'String(describing:)' to silence this warning Fix
Provide a default value to avoid this warning Fix
However, when I click one of Xcode's solutions I get another problem.
If I use the first fix, the app crashes and I get the following message:
Thread 1: Fatal error: Unexpected Segue Identifier;
If I use the second fix I have to assign a default value. And I don't know what this should be.
The whole passage of code is as follows.
It's the line starting with guard let selectedMealCell and the last one after default: that is causing the issues.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "AddItem":
os_log("Adding a new meal.", log: OSLog.default, type: .debug)
case "ShowDetail":
guard let mealDetailViewController = segue.destination as? MealViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedMealCell = sender as? MealTableViewCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedMealCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedMeal = meals[indexPath.row]
mealDetailViewController.meal = selectedMeal
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}
So, the first suggested fix worked for you. It quieted the compile time warning, although admittedly String(describing:) is a weak solution.
In both cases, you need to unwrap the optional value. For the first case you should use:
guard let selectedMealCell = sender as? MealTableViewCell else {
if let sender = sender {
fatalError("Unexpected sender: \(sender))")
} else {
fatalError("sender is nil")
}
}
and in the second case:
fatalError("Unexpected Segue Identifier; \(segue.identifier ?? "")")
Then you got a runtime error:
"Unexpected Segue Identifier;"
That is telling you that your switch didn't match the first 2 cases and it ran the default case. The crash is caused because your code is explicitly calling fatalError. Your segue.identifier is apparently an empty string.
So your problem is actually in your Storyboard. You need to assign identifiers to your segues. Click on the segue arrows between your view controllers, and assign identifiers "AddItem" and "ShowDetail" to the proper segues. The segue identifier is assigned in the Attributes Inspector on the right in Xcode.
If you are prepared to write an small extension to Optional, it can make the business of inserting the value of an optional variable less painful, and avoid having to write optionalVar ?? "" repeatedly:
Given:
extension Optional: CustomStringConvertible {
public var description: String {
switch self {
case .some(let wrappedValue):
return "\(wrappedValue)"
default:
return "<nil>"
}
}
}
Then you can write:
var optionalWithValue: String? = "Maybe"
var optionalWithoutValue: String?
print("optionalWithValue is \(optionalWithValue.description)")
print("optionalWithoutValue is \(optionalWithoutValue.description)")
which gives:
optionalWithValue is Maybe
optionalWithoutValue is <nil>
You can also write print("value is \(anOptionalVariable)") -- the .description is redundant since print() uses CustomStringConvertible.description anyway -- but although it works you still get the annoying compiler warning.
You can use the following to automatically produce "nil" (or any other String) for nil values and for non-nil values use the description provided by CustomStringConvertible
extension String.StringInterpolation {
mutating func appendInterpolation<T: CustomStringConvertible>(_ value: T?) {
appendInterpolation(value ?? "nil" as CustomStringConvertible)
}
}
For your own types you have to conform to CustomStringConvertible for this to work:
class MyClass: CustomStringConvertible {
var description: String {
return "Whatever you want to print when you use MyClass in a string"
}
}
With this set up, you can simply use your optionals the same way as any other type, without any compiler warnings.
var myClass: MyClass?
myClass = MyClass()
print("myClass is \(myClass)")

protocols & viewControllers, passing info using protocols on the same action Func Swift 3 & 4

I have this code to pass info between viewControllers using protocols, I have my models and with the first protocol work perfectly but the second one I have some issues, the data pass nil, or do nothing, already use tabBarController & UINavigationBar... the protocols... I create one in my ViewController(RecordViewController) and have the button & action and one protocol, this is:
protocol RecordViewProtocol {
func newTrackInstrument(item: SampleCarouselsRecord)
func newInstrumentTrackCell(item: SampleTrackCellRecord)
}
extension RecordViewController: RecordViewProtocol {
func newTrackInstrument(item: SampleCarouselsRecord) {
self.sampleCarouselItemsRecord.append(item)
let indexOnPath = NSIndexPath(row: self.sampleCarouselItemsRecord.count - 1, section: 0)
self.instrumentCarousel.insertItems(at: [indexOnPath as IndexPath])
}
func newInstrumentTrackCell(item: SampleTrackCellRecord) {
self.sampleTrackRecord.append(item)
let indexOnPath = NSIndexPath(row: self.sampleTrackRecord.count - 1, section: 0)
self.trackInstrument.insertItems(at: [indexOnPath as IndexPath])
}
}
the other protocol, on another ViewController(MixerViewController):...
protocol MixerViewProtocol {
func newCarouselItem(item: SampleCarouselMixer)
}
extension MixerViewController: MixerViewProtocol {
func newCarouselItem(item: SampleCarouselMixer) {
self.sampleCarouselMixer.append(item)
let indexOnPath = NSIndexPath(row: self.sampleCarouselMixer.count - 1, section: 0)
self.mixerCarouselInstrument.insertItems(at: [indexOnPath as IndexPath])
}
}
so, when wanna go the view and put the info to my models.. I put this code in the action Button to go into the view where is the function to put the info models:
#objc func goToAddView() {
let addTrackViewController = AddNewTrackView()
let addTrackView = UINavigationController(rootViewController: addTrackViewController)
addTrackViewController.recordViewControllerProtocol = self as RecordViewProtocol
present(addTrackView, animated: true, completion: nil)
}
and work good with the first protocol.. but with to the other protocol nothing.. I don't know why or I don't know wath I have to do... look, I have the delegate protocols inside on the AddNweTrackView:
var recordViewControllerProtocol: RecordViewProtocol?
var delegateMixerView: MixerViewProtocol? <- this is I think, put into the action button like the firstone:
here on the same action where the first protocol already is
#objc func goToAddView() {
let addTrackViewController = AddNewTrackView()
let addTrackView = UINavigationController(rootViewController: addTrackViewController)
addTrackViewController.recordViewControllerProtocol = self as RecordViewProtocol
addTrackViewController.delegateMixerView = MixerViewController.self as? MixerViewProtocol
present(addTrackView, animated: true, completion: nil)
}
but return nil... and I try with diferents forms.. I try put this:
let mixerViewController = MixerViewController()
addTrackViewController.delegateMixerView = mixerViewController as? MixerViewProtocol
and try with this but the error Is obvious:
addTrackViewController.delegateMixerView = self as MixerProtocol
and the same... I think, here at this point, it's where Im failing .. someone can help?

Does this view controller leak in a "willSet/didSet" pair?

You have a vc (green) and it has a panel (yellow) "holder"
Say you have ten different view controllers...Prices, Sales, Stock, Trucks, Drivers, Palettes, which you are going to put in the yellow area, one at a time. It will dynamically load each VC from storyboard
instantiateViewController(withIdentifier: "PricesID") as! Prices
We will hold the current VC one in current. Here's code that will allow you to "swap between" them...
>>NOTE, THIS IS WRONG. DON'T USE THIS CODE<<
One has to do what Sulthan explains below.
var current: UIViewController? = nil {
willSet {
// recall that the property name ("current") means the "old" one in willSet
if (current != nil) {
current!.willMove(toParentViewController: nil)
current!.view.removeFromSuperview()
current!.removeFromParentViewController()
// "!! point X !!"
}
}
didSet {
// recall that the property name ("current") means the "new" one in didSet
if (current != nil) {
current!.willMove(toParentViewController: self)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
current!.didMove(toParentViewController: self)
}
}
}
>>>>>>>>IMPORTANT!<<<<<<<<<
Also note, if you do something like this, it is ESSENTIAL to get rid of the yellow view controller when the green page is done. Otherwise current will retain it and the green page will never be released:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
current = nil
super.dismiss(animated: flag, completion: completion)
}
Continuing, you'd use the current property like this:
func showPrices() {
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
}
func showSales() {
current = s.instantiateViewController(withIdentifier: "SalesID") as! Sales
}
But consider this, notice "point X". Normally there you'd be sure to set the view controller you are getting rid of to nil.
blah this, blah that
blah.removeFromParentViewController()
blah = nil
However I (don't think) you can really set current to nil inside the "willSet" code block. And I appreciate it's just about to be set to something (in didSet). But it seems a bit strange. What's missing? Can you even do this sort of thing in a computed property?
Final usable version.....
Using Sulthan's approach, this then works perfectly after considerable testing.
So calling like this
// change yellow area to "Prices"
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
// change yellow area to "Stock"
current = s.instantiateViewController(withIdentifier: "StickID") as! Stock
this works well...
var current: UIViewController? = nil { // ESSENTIAL to nil on dismiss
didSet {
guard current != oldValue else { return }
oldValue?.willMove(toParentViewController: nil)
if (current != nil) {
addChildViewController(current!)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
}
oldValue?.view.removeFromSuperview()
oldValue?.removeFromParentViewController()
if (current != nil) {
current!.didMove(toParentViewController: self)
}
}
// courtesy http://stackoverflow.com/a/41900263/294884
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// ESSENTIAL to nil on dismiss
current = nil
super.dismiss(animated: flag, completion: completion)
}
Let's divide the question into two: (1) Is there a "leak"? and (2) Is this a good idea?
First the "leak". Short answer: no. Even if you don't set current to nil, the view controller it holds obviously doesn't "leak"; when the containing view controller goes out of existence, so does the view controller pointed to by current.
The current view controller does, however, live longer than it needs to. For that reason, this seems a silly thing to do. There is no need for a strong reference current to the child view controller, because it is, after all, your childViewControllers[0] (if you do the child view controller "dance" correctly). You are thus merely duplicating, with your property, what the childViewControllers property already does.
So that brings us to the second question: is what you are doing a good idea? No. I see where you're coming from — you'd like to encapsulate the "dance" for child view controllers. But you are doing the dance incorrectly in any case; you're thus subverting the view controller hierarchy. To encapsulate the "dance", I would say you are much better off doing the dance correctly and supplying functions that perform it, along with a computed read-only property that refers to childViewController[0] if it exists.
Here, I assume we will only ever have one child view controller at a time; I think this does much better the thing you are trying to do:
var current : UIViewController? {
if self.childViewControllers.count > 0 {
return self.childViewControllers[0]
}
return nil
}
func removeChild() {
if let vc = self.current {
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
}
func createChild(_ vc:UIViewController) {
self.removeChild() // There Can Be Only One
self.addChildViewController(vc) // *
// ... get vc's view into the interface ...
vc.didMove(toParentViewController: self)
}
I don't think that using didSet is actually wrong. However, the biggest problem is that you are trying to split the code between willSet and didSet because that's not needed at all. You can always use oldValue in didSet:
var current: UIViewController? = nil {
didSet {
guard current != oldValue else {
return
}
oldValue?.willMove(toParentViewController: nil)
if let current = current {
self.addChildViewController(current)
}
//... add current.view to the view hierarchy here...
oldValue?.view.removeFromSuperview()
oldValue?.removeFromParentViewController()
current?.didMove(toParentViewController: self)
}
}
By the way, the order in which the functions are called is important. Therefore I don't advise to split the functionality into remove and add. Otherwise the order of viewDidDisappear and viewDidAppear for both controllers can be surprising.

Swift2: return a optional type object

I'm new in the swift2 world and I currently struggle with a simple function :
// Get all moves for the first category
func getMuscles() -> BodyPart {
let bpart:BodyPart?
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
do{
let fetchRequest = NSFetchRequest(entityName: "BodyPart")
let fetchResults = try managedObjectContext.executeFetchRequest(fetchRequest) as! [BodyPart]
bpart = fetchResults[0]
} catch let error as NSError {
print(error)
bpart = nil
}
}
return bpart
}
How can I solve this issue ? And what are the 'best-practices' in swift2 for defining a function ?
Thank you
EDIT 1
I've tried to change the signature of the method, but error is still here :
The question you should be asking yourself is whether getMuscles() must always return an object or if it's fine for it to return a nil.
By changing the method signature to func getMuscles() -> BodyPart?,
you're basically stating that a nil might be returned from that method,
thus solving your immediate compile time issue.
In that particular context, because you're fetching objects from CoreData,
it might be wise to allow getMuscles() to return a nil.
The way you define your functions (if they return optionals ?, or not) entirely depends on the calling code.
Change your method signature to :
func getMuscles() -> BodyPart?
But be careful while unwrapping the return value when the this function is being called.
Just return:
func getMuscles() -> BodyPart? { }
Thats nothing to do with SWIFT2.. The return type is expecting some value BodyPart not an optional value BodyPart?...But you are returning a optional value bpart
func getMuscles() -> BodyPart {
let bpart:BodyPart?
....
return bpart
}
If you want to return bpart as it is you need to create the return type as optional
func getMuscles() -> BodyPart? {
let bpart:BodyPart?
....
return bpart
}
or if you want to just return the value try this
func getMuscles() -> BodyPart {
let bpart:BodyPart = ()//initialize here dont make optional
....
return bpart
}