I'm trying to understand where this code went wrong given the code below.
In the code below, I'm trying to locate a UIViewController of a specific class in the UITabBarController's viewControllers property which is declared as:
var viewControllers: [AnyObject]?
So I'm defining two UIViewController subclasses and stuffing them to the viewControllers array and running two different methods to extract them "viewControllerInfo:" ""viewControllerInfo2".
Which both yield the same result.
My understanding is the:
if let x as? T will evaluate true and assign x as a "T" type if it is the same class.
Just as if x is T would.
Any idea why evaluation is behaving like this ?
class VC1: UIViewController {}
class VC2: UIViewController {}
let tabBar = UITabBarController()
tabBar.viewControllers = [VC1(), VC2()]
extension UITabBarController {
public func viewControllerInfo<T: UIViewController>(ofType: T.Type) -> (viewController: T,index: Int)? {
if let tabViewControllers = self.viewControllers{
for (idx, maybeVCTypeT) in enumerate(tabViewControllers) {
if let viewController = maybeVCTypeT as? T {
return (viewController, idx)
}
}
}
return nil
}
public func viewControllerInfo2<T: UIViewController>(ofType: T.Type) -> (viewController: T,index: Int)? {
if let tabViewControllers = self.viewControllers{
for (idx, maybeVCTypeT) in enumerate(tabViewControllers) {
if maybeVCTypeT is T {
return (maybeVCTypeT as! T, idx)
}
}
}
return nil
}
}
All the tests below will end up giving exactly the same result:
"<__lldb_expr_89.VC1: 0x7f85016079f0>"
if let (vc, idx) = tabBar.viewControllerInfo(VC1.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo(VC2.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo2(VC1.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo2(VC2.self) {
println(vc)
}
I'm suspicious of the enumerate(x) since without it I'm getting expected results:
if testVC1 is VC2 {
println("\(testVC1) is \(VC2.self)")
}
the above code yields a warning:
Cast from 'VC1' to unrelated type 'VC2' always fails
Which is what i'm trying to achieve with enumerate...
***************** EDIT *****************
Actually my suspicion of enumerate is dissolved after running the following code which ran perfectly.
let myArray: [AnyObject] = [VC2(), VC1()]
for (idx, x) in enumerate(myArray) {
println(x)
if let xAsVC1 = x as? VC1 {
println("\(x) is supposed to be \(VC1.self)")
//"<__lldb_expr_155.VC1: 0x7fc12a9012f0> is supposed to be __lldb_expr_155.VC1"
}
if x is VC2 {
println("\(x) is supposed to be \(VC2.self)")
//"<__lldb_expr_155.VC2: 0x7fc12a900fd0> is supposed to be __lldb_expr_155.VC2"
}
}
This seems to be caused by the generic constraint, and I believe is a bug (http://www.openradar.me/22218124). Removing the generic constraint, or making it a non-ObjC class (such as AnyObject) seems to fix it:
public func viewControllerInfo<T>(ofType: T.Type) -> (viewController: T,index: Int)? {
You can also replace:
if maybeVCTypeT is T {
with:
if maybeVCTypeT.isKindOfClass(T) {
And that seems to work.
Related
I am sending the viewController object to the manager class and reaching the views in the viewController with refleciton. However, if I define viewController optional, it cannot find reflection views.
class ClientManager {
var viewController : UIViewController? // not working var viewController : UIViewController is working why?
init (_ viewController: UIViewController) {
self.viewController = viewController
}
....
}
My reflection method is ;
private func prepareTarget(obj: Any, selector : String?,cellIdentifier :String?)
-> UIView?
{
let mirror = Mirror(reflecting: obj)
var result : UIView?
for (prop, val) in mirror.children {
if prop == selector {
result = val as? UIView
break
}
if val is UITableView {
let visibleCells = (val as! UITableView).visibleCells
for cell in visibleCells {
if result == nil {
result = prepareTarget(obj: cell,selector:
selector, cellIdentifier: cellIdentifier)
}
}
}
return result
}
How can I solve this problem?
I want to create an array of weak referenced delegates like so...
fileprivate class WeakDelegate<T:AnyObject> {
weak var value:T?
init (value:T) {
self.value = value
}
}
class Radio {
private var delegates = [WeakDelegate<AnyObject>]()
}
So far so good...? what I'd like to do also is tidy up my array in these two ways...
1.
func removeDeadDelegates() {
let liveDelegates = delegates.filter { $0.value != nil }
delegates = liveDelegates
}
and 2.
func remove<T>(specificDelegate:T) {
let filteredDelegates = delegates.filter { $0.value != specificDelegate }
listeners = filteredDelegates
}
Says Cannot convert value of type 'T' to expected argument type '_OptionalNilComparisonType'
Now I can just add this to make the warning go away like this...
let liveDelegates = delegates.filter {
if let d = specificDelegate as? _OptionalNilComparisonType {
return $0.value != d
}
return true
}
but this cast doesn't work...
I'm concerned because I'm not sure what this means... can anyone explain why I can't compare generics with == and why this cast is failing?
Thanks for your time
EDIT
Like this?
func remove<T:AnyObject>(delegate:T) {
let filteredDelegates = delegates.filter { $0.value != delegate }
delegates = filteredDelegates
}
No joy sadly...
Instances of a class type can be compared with the “identical to”
=== and “not identical to” !== operators:
func remove(specificDelegate: AnyObject) {
let filteredDelegates = delegates.filter { $0.value !== specificDelegate }
delegates = filteredDelegates
}
The same works for a generic method
func remove<T:AnyObject>(specificDelegate: T) {
let filteredDelegates = delegates.filter { $0.value !== specificDelegate }
delegates = filteredDelegates
}
(but I do not yet see the advantage of doing so).
I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
var migrationsList = [Preparation.Type]()
var count = UInt32(0)
let classList = objc_copyClassList(&count)!
for i in 0..<Int(count) {
let classInfo = ClassInfo(classList[i])!
if let cls = classInfo.classObject as? Migration.Type {
migrationsList.append(cls)
print(cls.description)
}
}
return migrationsList
}
}
In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.
Any idea what I got wrong with that code?
I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...
EDIT: I forgot to provide the code for ClassInfo
import Foundation
struct ClassInfo: CustomStringConvertible, Equatable {
let classObject: AnyClass
let className: String
init?(_ classObject: AnyClass?) {
guard classObject != nil else { return nil }
self.classObject = classObject!
let cName = class_getName(classObject)!
self.className = String(cString: cName)
}
var superclassInfo: ClassInfo? {
let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
return ClassInfo(superclassObject)
}
var description: String {
return self.className
}
static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
return lhs.className == rhs.className
}
}
I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.
Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
}
static func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
let classes = objc_getClassList()
var ret = [AnyClass]()
for cls in classes {
if class_conformsToProtocol(cls, p) {
ret.append(cls)
}
}
return ret
}
static func objc_getClassList() -> [AnyClass] {
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyClass]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)] {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
class Migration: Preparation {
}
#objc
protocol Preparation {
}
I've got an NSViewController extension that iterates through all its descendant child view controllers looking for the first view controller that passes a particular test (specified by a user-defined block):
extension NSViewController {
func descendantViewControllerPassingTest(test: (viewController: NSViewController) -> Bool) -> NSViewController? {
var retval: NSViewController?
for childViewController in childViewControllers {
if test(viewController: childViewController) {
retval = childViewController
} else if let descendantViewController = viewController.descendantViewControllerPassingTest(test) {
retval = descendantViewController
}
if retval != nil { break }
}
return retval
}
}
Ninety-nine percent of the time my tests consist of a simple type-check...
contentViewController.descendantViewControllerPassingTest {$0 is OutlineViewController}
...so I'd like to create a short convenience extension that does this:
extension NSViewController {
func descendantViewControllerMatchingType(type: WHAT_HERE?) {
return descendantViewControllerPassingTest({ $0 is WHAT_HERE? })
}
}
But I can't work out what type my parameter type should be. I've tried AnyClass and AnyObject but the compiler informs me that these aren't types:
Closures is what I'd use. Pass in your object to the closure with a return type of BOOL. Use the closure to match your descendant recursively.
I guess I'm struggling with generics. I want to create simple UIView extension to find recursively a superview of class passed in the function param. I want the function to return optional containing obviously either nil, or object visible as instance of provided class.
extension UIView {
func superviewOfClass<T>(ofClass: T.Type) -> T? {
var currentView: UIView? = self
while currentView != nil {
if currentView is T {
break
} else {
currentView = currentView?.superview
}
}
return currentView as? T
}
}
Any help much appreciated.
Swift 3/4
This is a more concise way:
extension UIView {
func superview<T>(of type: T.Type) -> T? {
return superview as? T ?? superview.compactMap { $0.superview(of: type) }
}
func subview<T>(of type: T.Type) -> T? {
return subviews.compactMap { $0 as? T ?? $0.subview(of: type) }.first
}
}
Usage:
let tableView = someView.superview(of: UITableView.self)
let tableView = someView.subview(of: UITableView.self)
No need to pass in the type of the class you want (at least in Swift 4.1)…
extension UIView {
func firstSubview<T: UIView>() -> T? {
return subviews.compactMap { $0 as? T ?? $0.firstSubview() as? T }.first
}
}
I'm using this.
// Lookup view ancestry for any `UIScrollView`.
if let scrollView = view.searchViewAnchestors(for: UIScrollView.self) {
print("Found scrollView: \(scrollView)")
}
Extension is really a single statement.
extension UIView {
func searchViewAnchestors<ViewType: UIView>(for viewType: ViewType.Type) -> ViewType? {
if let matchingView = self.superview as? ViewType {
return matchingView
} else {
return superview?.searchViewAnchestors(for: viewType)
}
}
}
With this alternative implementation below, you can actually let the call site determine what type to look for, but I found it somewhat unconventional.
extension UIView {
func searchInViewAnchestors<ViewType: UIView>() -> ViewType? {
if let matchingView = self.superview as? ViewType {
return matchingView
} else {
return superview?.searchInViewAnchestors()
}
}
}
You can call it like this.
if let scrollView: UIScrollView = view.searchInViewAnchestors() {
print("Found scrollView: \(scrollView)")
}