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)")
}
Related
Need to wrap the extension for the view controller instantiating inside the dispatch main thread, but got that error, any ideas how to resolve it?
extension UIStoryboard {
convenience init(name: StoryboardName) {
self.init(name: name.rawValue, bundle: nil)
}
func instantiateVC<T: UIViewController>(identifier: String = T.identifier) -> T {
// swiftlint:disable force_cast
DispatchQueue.main.async {
let controller = self.instantiateViewController(withIdentifier: identifier) as! T
controller.removeBackButtonTitle()
return controller
}
// swiftlint:enable force_cast
}
func instantiateInitialVC() -> UIViewController {
return self.instantiateInitialViewController()!
}
}
instantiateVC should not return a value. You need to add a new argument to this function to pass controller:
func instantiateVC<T: UIViewController>(identifier: String = T.identifier, completion: #escaping (T) -> Void) {
// swiftlint:disable force_cast
DispatchQueue.main.async {
let controller = self.instantiateViewController(withIdentifier: identifier) as! T
controller.removeBackButtonTitle()
completion(controller)
}
// swiftlint:enable force_cast
}
I have such code a little modified from code of Eric Armstrong
Adding a closure as target to a UIButton
But there is the problem with both codes. Those from Eric does remove all target-actions on
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside)
And modified code on the other hand do not remove target-actions at all. Of course it is caused by if condition, but it also means that there are no targets stored properly in Storable property.
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
let key = String(describing: type(of: Storable.self))
guard let obj = objc_getAssociatedObject(self, key) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, key, property, .OBJC_ASSOCIATION_RETAIN)
}
return objc_getAssociatedObject(self, key) as? Storable ?? Property.property
}
return obj
}
set {
let key = String(describing: type(of: Storable.self))
return objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
My aim is to precisely register target-actions with closures and remove them without removing all other target-actions added to given UITextField via #selector. Now I can have removed ALL or NONE of target-actions while using this approach for closure-style target actions.
UPDATE
Based on Eric Armstrong answer i have implemented my version.
But what I have experienced in version proposed by Eric was that when adding target actions to TextField on TableView list while cells appear and then removing this target actions from Text Fields while cells diseappear the previous code seems to remove all target actions on removeTarget(for:) exection. So when in other place in code like UITableViewCell I have added additional target action on totaly different target (UITableViewCell object, not this custom Target() objects) while cells was disappearing and then again appearing on screen and removeTarget(for) was executed then this other (external as I call them target actions) also was removed and never called again.
I consider that some problem was usage of [String: Target] dictionary which is value type and it was used in case of property getter in objc_getAssociatedObject where there was
objc_getAssociatedObject(self, key) as? Storable ?? Property.property
So as I understand it then there wasn't objc object for given key and Storable was nil and nil-coalescing operator was called and static value type Property.property return aka [String : Dictionary]
So it was returned by copy and Target object was stored in this copied object which wasn't permanently stored and accessed in removeTarget(for:) always as nil. So nil was passed to UIControl.removetTarget() and all target actions was always cleared!.
I have tried simple replacing [String: Target] Swift dictionary with NSMutableDictionary which is a reference type so I assume it can be stored. But this simple replacement for static variable and just returning it via nil-coalesing operator caused as I assume that there as only one such storage for Target objects and then while scrolling Table View each removeForTarget() has somehow remove all target actions from all UITextFields not only from current.
I also consider usage of String(describing: type(of: Storable.self)) as being wrong as it will be always the same for given Storable type.
Ok, I think I finally solved this issue
The main problem was usage of AssociatedKey! it needs to be done like below
https://stackoverflow.com/a/48731142/4415642
So I ended up with such code:
import UIKit
/**
* Swift 4.2 for UIControl and UIGestureRecognizer,
* and and remove targets through swift extension
* stored property paradigm.
* https://stackoverflow.com/a/52796515/4415642
**/
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
print("ADDED \(ObjectIdentifier(target)), \(target.action)")
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
print("REMOVE \(ObjectIdentifier(target)), \(target.action)")
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
extension UIGestureRecognizer: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property: Target?
}
func addTarget(target: #escaping (Any) -> ()) {
let target = Target(target: target)
addTarget(target, action: target.action)
property = target
}
func removeTarget() {
let target = property
removeTarget(target, action: target?.action)
property = nil
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
deinit {
print("Deinit target: \(ObjectIdentifier(self))")
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
guard let obj = objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, &AssociatedKeys.property, property, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable ?? Property.property
}
return obj
}
set {
return objc_setAssociatedObject(self, &AssociatedKeys.property, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
private struct AssociatedKeys {
static var property = "AssociatedKeys.property"
}
I am trying to make a function that, by given a UIView, it iterates recursively to all the subviews until it finds a UIView of type T. So far I have:
func getType(from view: UIView) -> AdaptiveContainerView? {
for aView in view.subviews {
if let adapView = aView as? AdaptiveContainerView {
return adapView
}
else {
return getType(from: aView)
}
}
return nil
}
Now I am trying to refractor the function so it gets the UIView type and returns it if found. By UIView type I mean:
class MyView: UIView {}
My first approach is
func getGenericType<T, Q:UIView>(from view: UIView, ofType: T) -> Q? {
for aView in view.subviews {
if aView is ofType {
}
...
}
return nil
}
However I am having the following error:
Use of undeclared type 'ofType'
Any ideas?
Thank you
For Static
func getGenericType<Q:MyView>(from view: UIView) -> Q? {
for aView in view.subviews {
if let res = aView as? Q {
return res
}
else {
return getGenericType(from: aView)
}
}
return nil
}
Call
let res = getGenericType(from: self.view) // res of type MyView
If you need to dynamically send the parameter
func getGenericType<T:UIView>(from view: UIView,type:T.Type) -> T? {
for aView in view.subviews {
if let res = aView as? T {
return res
}
else {
return getGenericType(from: aView,type:T.self)
}
}
return nil
}
Call
let res = getGenericType(from: self.view,type:MyView.self) // res of type MyView
Does function can return specific object, when I input a specific class.
My problem:
I don't know how to return a object. take a look following code and thanks
class MyViewControler {
}
class MySplitViewController: NSSplitViewControler {
override func viewDidLoad() {
/*
* get specific object
*/
let vc = viewController(for: MyViewControler.self)
}
}
extension NSSplitViewController {
public func viewController<T>(for anClass: T) -> T.object {
guard let tClass = anClass as? AnyClass else { return nil }
var vc: NSViewController?
if let idx = self.splitViewItems.index(where: { $0.viewController.classForCoder == tClass} ) {
vc = self.splitViewItems[idx].viewController
}
}
/*
* I don't know how to return a specific object
*/
return vc
}
The signature of a method taking a type and returning an
(optional) instance of that type would be:
public func viewController<T>(for aClass: T.Type) -> T?
or, if you want to restrict it to subclasses of NSViewController:
public func viewController<T: NSViewController>(for aClass: T.Type) -> T?
The implementation can be simplified with optional binding:
extension NSSplitViewController {
public func viewController<T: NSViewController>(for aClass: T.Type) -> T? {
for item in self.splitViewItems {
if let vc = item.viewController as? T {
return vc
}
}
return nil
}
}
Or as a "one-liner":
extension NSSplitViewController {
public func viewController<T: NSViewController>(for aClass: T.Type) -> T? {
return self.splitViewItems.lazy.flatMap { $0.viewController as? T }.first
}
}
Can you tell me why this code does not work?
I have several arrays of [AnyObject] that contain UILabels and UITextForm.
This func should take as parameter an array and make all the labels and the text form disabled. I've tried with map, but still i have the same problem, the compiler tells me that or the variable is a constant or that is immutable.
func disableSectionForm(formSection section: inout [AnyObject]) {
for i in 0...section.count {
if section[i] is UILabel || section[i] is UITextField {
section[i].isEnabled = false
}
}
}
There are many compile errors here
Issue #1 (this is just a suggestion)
inout is not needed here because you are not mutating the section array, you are mutating the objects inside it instead.
Issue #2
The inout should go before the param name (if you are using Swift 2.2)
Issue #3
You should use self when comparing with dynamicType
Issue #4
You can't write section[i].isEnabled = false because AnyObject has no member isEnabled so you should do a cast
Issue #5
You are accessing an index outside of your array so this
0...section.count
should become this
0..<section.count
Code Version #1
Now your code looks like this
func disableSectionForm(formSection section: [AnyObject]) {
for i in 0..<section.count {
if section[i].dynamicType == UILabel.self {
(section[i] as? UILabel)?.enabled = false
} else if section[i].dynamicType == UITextField.self {
(section[i] as? UITextField)?.enabled = false
}
}
}
Code Version #2
Since:
you can iterate your elements in a safer way
you should use conditional cast instead of dynamicType comparation
you can write in
Swift 2.2
func disableSectionForm(formSection section: [AnyObject]) {
section.forEach {
switch $0 {
case let label as UILabel: label.enabled = false
case let textField as UITextField: textField.enabled = false
default: break
}
}
}
Swift 3.0 (beta 6)
func disableSectionForm(formSection section: [Any]) {
section.forEach {
switch $0 {
case let label as UILabel: label.isEnabled = false
case let textField as UITextField: textField.isEnabled = false
default: break
}
}
}
Code Version #3
Let's define a protocol to represents classes with an enabled Bool property.
Swift 2.2
protocol HasEnabledProperty:class {
var enabled: Bool { get set }
}
Let's conform to it UILabel and UITextLabel
extension UILabel: HasEnabledProperty { }
extension UITextField: HasEnabledProperty { }
And finally...
func disableSectionForm(formSection section: [AnyObject]) {
section.flatMap { $0 as? HasEnabledProperty }.forEach { $0.enabled = false }
}
Swift 3.0 (beta 6)
protocol HasEnabledProperty:class {
var isEnabled: Bool { get set }
}
extension UILabel: HasEnabledProperty { }
extension UITextField: HasEnabledProperty { }
func disableSectionForm(formSection section: [Any]) {
section.flatMap { $0 as? HasEnabledProperty }.forEach { $0.isEnabled = false }
}
try to check if let block and use optionals
func disableSectionForm(formSection section: inout [AnyObject]) {
for i in 0...section.count {
if let label = section[i] as? UILabel {
label.isEnabled = false
}
if let textField = section[i] as? UITextFiled {
textField.isEnabled = false
}
}
}