I would like to traverse the view controller hierarchy in Swift and find a particular class. Here is the code:
extension UIViewController{
func traverseAndFindClass<T : UIViewController>() -> UIViewController?{
var parentController = self.parentViewController as? T?
^
|
|
// Error: Could not find a user-defined conversion from type 'UIViewController?' to type 'UIViewController'
while(parentController != nil){
parentController = parentController!.parentViewController
}
return parentController
}
}
Now, I know that the parentViewController property returns an optional UIViewController, but I do not know how in the name of God I can make the Generic an optional type. Maybe use a where clause of some kind ?
Your method should return T? instead of UIViewController?, so that the generic type
can be inferred from the context. Checking for the wanted class has also to
be done inside the loop, not only once before the loop.
This should work:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parentViewController {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
Example usage:
if let vc = self.traverseAndFindClass() as SpecialViewController? {
// ....
}
Update: The above method does not work as expected (at least not in the Debug
configuration) and I have posted the problem
as a separate question: Optional binding succeeds if it shouldn't. One possible workaround (from an answer to that question)
seems to be to replace
if let result = parentVC as? T { ...
with
if let result = parentVC as Any as? T { ...
or to remove the type constraint in the method definition:
func traverseAndFindClass<T>() -> T? {
Update 2: The problem has been fixed with Xcode 7, the
traverseAndFindClass() method now works correctly.
Swift 4 update:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parent {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
One liner solution (using recursion), Swift 4.1+:
extension UIViewController {
func findParentController<T: UIViewController>() -> T? {
return self is T ? self as? T : self.parent?.findParentController() as T?
}
}
Example usage:
if let vc = self.findParentController() as SpecialViewController? {
// ....
}
Instead of while loops, we could use recursion (please note that none of the following code is thoroughly tested):
// Swift 2.3
public extension UIViewController {
public var topViewController: UIViewController {
let o = topPresentedViewController
return o.childViewControllers.last?.topViewController ?? o
}
public var topPresentedViewController: UIViewController {
return presentedViewController?.topPresentedViewController ?? self
}
}
On the more general issue of traversing the view controller hierarchy, a possible approach is to have two dedicated sequences, so that we can:
for ancestor in vc.ancestors {
//...
}
or:
for descendant in vc.descendants {
//...
}
where:
public extension UIViewController {
public var ancestors: UIViewControllerAncestors {
return UIViewControllerAncestors(of: self)
}
public var descendants: UIViewControllerDescendants {
return UIViewControllerDescendants(of: self)
}
}
Implementing ancestor sequence:
public struct UIViewControllerAncestors: GeneratorType, SequenceType {
private weak var vc: UIViewController?
public mutating func next() -> UIViewController? {
guard let vc = vc?.parentViewController ?? vc?.presentingViewController else {
return nil
}
self.vc = vc
return vc
}
public init(of vc: UIViewController) {
self.vc = vc
}
}
Implementing descendant sequence:
public struct UIViewControllerDescendants: GeneratorType, SequenceType {
private weak var root: UIViewController?
private var index = -1
private var nextDescendant: (() -> UIViewController?)? // TODO: `Descendants?` when Swift allows recursive type definitions
public mutating func next() -> UIViewController? {
if let vc = nextDescendant?() {
return vc
}
guard let root = root else {
return nil
}
while index < root.childViewControllers.endIndex - 1 {
index += 1
let vc = root.childViewControllers[index]
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
guard let vc = root.presentedViewController where root === vc.presentingViewController else {
return nil
}
self.root = nil
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
public init(of vc: UIViewController) {
root = vc
}
}
Related
I'm trying to pass data between viewControllers, but something seems wrong.
The first viewController I want to set the "Bool" to the protocol function to be able to recover in the other screen. What am I doing wrong, I always used protocols but at this time I got in trouble.
That's how I'm doing that:
//
// ComboBoxNode.swift
//
import Foundation
import SWXMLHash
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool)
}
class ComboBoxNode: FormControlNode, IFormControlDataSource {
var listType: String?
var dataSource: String?
var dataSourceValue: String?
var dataSourceText: String?
var hasCustomOption:Bool?
var customOptionText: String?
var ctrlDataSourceType: String?
var parameters = [ParameterNode]()
var staticList: FormControlStaticListNode?
var delegate:ComboBoxNodeDelegate?
override init(indexer: XMLIndexer) {
super.init(indexer: indexer)
guard let element = indexer.element else {
preconditionFailure("Error")
}
let isCustomOption = element.bool(by: .hasCustomOption) ?? hasCustomOption
if isCustomOption == true {
self.delegate?.getCustomOption(data: hasCustomOption!)
}
self.readFormControlDataSource(indexer: indexer)
}
override func accept<T, E: IViewVisitor>(visitor: E) -> T where E.T == T {
return visitor.visit(node: self)
}
}
That's how I'm trying to recover on next screen:
// FormPickerViewDelegate.swift
import Foundation
import ViewLib
import RxSwift
class FormPickerViewDelegate: NSObject {
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
private var controlViewModel: FormControlViewModel
private var customText:Bool?
private var PickerNodeDelegate:ComboBoxNodeDelegate?
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
}
func getItemByValue(_ value: Any) -> (AnyHashable, String)? {
if value is AnyHashable {
let found = items.value.filter {$0.value == value as! AnyHashable}
if found.count >= 1 {
return found[0]
}
}
return nil
}
}
extension FormPickerViewDelegate:ComboBoxNodeDelegate {
func getCustomOption(data: Bool) {
customText = data
}
}
Instead of setting PickerNodeDelegate = self in didSet {} closure
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
Assign it in your init() function instead
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
PickerNodeDelegate = self
}
Note, your should declare your delegate to be weak also, since it's a delegate, your protocol should conform to be a class type in order to be weakified.
protocol ComboBoxNodeDelegate: class
...
weak var delegate: ComboBoxNodeDelegate?
Here is an example, hope it helps!
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool) -> String
}
class ViewOne:ComboBoxNodeDelegate {
var foo:Bool = false
var bar:String = "it works!"
/** Return: String */
func getCustomOption(data:Bool) -> String { //conform here to protocol
// do whatever you wanna do here ...example
self.foo = data // you can set
return bar // even return what you want
}
//initialize
func initalizeViewTwo() {
let v2 = ViewTwo()
v2.delegate = self //since `self` conforms to the ComboBoxNodeDelegate protcol you are allowed to set
}
}
class ViewTwo {
var delegate:ComboBoxNodeDelegate?
func getCustomOption_forV1() {
let view2_foo = delegate.getCustomOption(data:true)
print(view2_foo) // should print "it works!"
}
}
All parameters passed around in Swift are constants -- so you cannot change them.
If you want to change them in a function, you must declare your protocol to pass by reference with inout:
protocol ComboBoxNodeDelegate {
func getCustomOption(data: inout Bool)
}
Note: you cannot pass a constant (let) to this function. It must be a variable -- which I see you are doing!
I'm using Swift 3 in Xcode 8.3.3.
I have 2 view controllers, A and B that inherit from a regular UIViewController like this:
class A: UIViewController {}
class B: UIViewController {}
These 2 view controllers can be presented by a base view controller in a UINavigationController.
I have a different view controller, Test, which will eventually be presented by either A or B, meaning somewhere at the end of the hierarchy like this:
Base -> A or B -> Other -> Other -> Test
Test will present it's data differently depending on which one it came from.
I have an extension on UIViewController that provides a way of checking this, and it works just fine when I hardcode it for A and B:
extension UIViewController {
var isFromA: Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? A) != nil }).count > 0
} else {
if let _ = parent as? A {
return true
} else if parent != nil {
return parent!.isFromA
} else {
return false
}
}
}
var isFromB: Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? B) != nil }).count > 0
} else {
if let _ = parent as? B {
return true
} else if parent != nil {
return parent!.isFromB
} else {
return false
}
}
}
}
In the Test lifecycle code, I can use it like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromA {
// do something
} else if isFromB {
// do something else
}
}
So now I'm going to be adding a new view controller, C, which can also eventually present a Test. I don't want to just copy and paste the code again when I make the isFromC var. I want to make a helper function that takes an instance of a type of class to use in those as? checks. The new code would look something like this:
extension UIViewController {
var isFromA: Bool {
return isFrom(A)
}
var isFromB: Bool {
return isFrom(B)
}
var isFromC: Bool {
return isFrom(C)
}
fileprivate func isFrom(_ viewControllerClass: UIViewController) -> Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? viewControllerClass) != nil }).count > 0
} else {
if let _ = parent as? viewControllerClass {
return true
} else if parent != nil {
return parent!.isFrom(viewControllerClass)
} else {
return false
}
}
}
}
This doesn't compile, and it's not quite right anyways, because I don't have an actual instance of A, B, or C to pass into this helper function.
So what's the best way to solve this? Also note that I'm open to suggestions on reworking the helper function code as well, as I'm not sure if it covers all the combinations of navigation controllers and such.
I figured out a way to pass a type as a parameter, after doing some searching. But the examples I was seeing didn't specifically cover my context (detecting if the currently presented view controller is downstream of another type of view controller in some way), so I decided to pose the question anyway, and provide my own answer. I'm still interested in any changes to the helper function, though.
But here's my solution:
extension UIViewController {
var isFromA: Bool {
return isFrom(viewControllerClassType: A.self)
}
var isFromB: Bool {
return isFrom(viewControllerClassType: B.self)
}
var isFromC: Bool {
return isFrom(viewControllerClassType: C.self)
}
fileprivate func isFrom(viewControllerClassType type: UIViewController.Type) -> Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ $0.isKind(of: type) }).count > 0
} else {
if let _ = parent?.isKind(of: type) {
return true
} else if parent != nil {
return parent!.isFrom(viewControllerClassType: type)
} else {
return false
}
}
}
}
I don't know if using UIViewController.Type is the best way to go, but it does the job.
You can modify what you have just a bit to use a generic type in your function and call it instead of your computed vars.
extension UIViewController {
func isFrom<T>(viewControllerClass: T) -> Bool {
guard let parent = parent else { return false }
if let nav = parent as? UINavigationController {
return nav.viewControllers.filter({ type(of: $0).self is T }).count > 0
} else {
if parent is T {
return true
} else {
return parent.isFrom(viewControllerClass: viewControllerClass)
}
}
}
}
Then call it with
if vc.isFrom(viewControllerClass: ClassA.self) {
// do something
} else if vc.isFrom(viewControllerClass: ClassB.self) {
// do something
}
That at least removes the need to add a new var for every parent class type.
I'm getting a segmentation fault when I try to compile the below code. I'm trying to make a type constrained extension on the CellUpdater struct, which accesses a property whose type is defined on an associatedtype on a generic type. Not sure if I'm doing something wrong or if it's a limitation of the Swift compiler, any ideas?
protocol CellUpdaterType {
func generateDetailsDrillDownController(index: Int) -> UIViewController?
}
extension CellUpdaterType {
func generateDetailsDrillDownController(index: Int) -> UIViewController? { return nil }
}
struct CellUpdater<Cell where Cell: UpdatableView> : CellUpdaterType {
let viewModel: Cell.ViewModel
}
extension CellUpdater where Cell: HeadlineCell {
func generateDetailsDrillDownController(index: Int) -> UIViewController? {
let storyboard = UIStoryboard(name: "SomeStoryboard", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SomeViewController") as? SomeViewController
vc?.headline = viewModel.headline // This line crashes the compiler
return vc
}
}
class HeadlineCell: UITableViewCell {
var headline: Headline?
// ...
}
extension HeadlineCell : UpdatableView {
typealias ViewModel = HeadlineCellViewModel
func update(viewModel viewModel: ViewModel) {
// ...
}
}
struct HeadlineCellViewModel {
let headline: Headline
init(headline: Headline) {
self.headline = headline
}
}
protocol UpdatableView: class {
associatedtype ViewModel
func update(viewModel viewModel: ViewModel)
}
I think it might be connected to compiler not being able to fix your view model type at compile time, which is required in Swift. It might have been fixed in Swift 3.0 though. I've manage to change your code a little so it now compiles. The major difference was to constraint view model instead of cell in CellUpdater extension.
struct CellUpdater<Cell where Cell: UpdatableView> : CellUpdaterType {
typealias ViewModel = Cell.ViewModel
let viewModel: ViewModel
}
extension CellUpdater where Cell.ViewModel : HeadlineCellViewModelType {
func generateDetailsDrillDownController(index: Int) -> UIViewController? {
let storyboard = UIStoryboard(name: "SomeStoryboard", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SomeViewController") as? SomeViewController
vc?.headline = viewModel.headline
return vc
}
}
protocol HeadlineCellViewModelType {
var headline: Headline { get }
}
struct HeadlineCellViewModel : HeadlineCellViewModelType {
let headline: Headline
init(headline: Headline) {
self.headline = headline
}
}
I have this protocol:
protocol ViewType {
associatedtype T: ViewData.View
var data:T! {get set}
}
ViewData.View is a class
I have a class called TemplateLabel that inherits UILabel and conforms to ViewType
class TemplateLabel:UILabel, ViewType {
var data: ViewData.View.Label!
}
I get this TemplateLabel from storyboard as a UIView and try to cast the UIView into ViewType to assign the data property to it
let view = SB.instantiateViewControllerWithIdentifier("view_label").view
if var v = view as? ViewType { // Error
v.data = data // Error
}
But I get errors:
Protocol 'ViewType' can only be used as a generic constraint because it has Self or associated type requirements
Member 'data' cannot be used on value of protocol type 'ViewType'; use a generic constraint instead
I have an answer for you, but that's pretty much bare code. I think it can really be useful in the defined context.
import UIKit
// Framework
/**
* Intended usage:
*
* enum AppStoryboard: BundledStoryboard {
*
* case Login
* case Main
*
* var storyboard: UIStoryboard {
* return UIStoryboard(name: "\(self)", bundle: nil)
* }
*
* }
*/
protocol BundledStoryboard {
var storyboard: UIStoryboard { get }
}
protocol StoryboardViewController {
static var bundledStoryboard: BundledStoryboard { get }
static var storyboardId: String { get }
static func instantiateFromStoryboard() -> Self
}
extension StoryboardViewController {
static var storyboardId: String { return "\(self)" }
static func instantiateFromStoryboard() -> Self {
return bundledStoryboard.storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! Self
}
}
// Application specific
enum AppStoryboard: BundledStoryboard {
//case Login
case Main
var storyboard: UIStoryboard {
return UIStoryboard(name: "\(self)", bundle: nil)
}
}
extension StoryboardViewController {
static var bundledStoryboard: BundledStoryboard { return AppStoryboard.Main }
}
// View-Model relation
protocol ViewType {
associatedtype Model
func loadModel(m: Model)
}
// ViewController
final class ViewController: UIViewController, StoryboardViewController, ViewType {
override func viewDidLoad() {
super.viewDidLoad()
}
func loadModel(m: UIColor?) {
view.backgroundColor = m // Strange example, yeah.
}
}
// Then somewhere...
let vc = ViewController.instantiateFromStoryboard()
vc.loadModel(.redColor())
I don't think you really need any dynamic solution here. You must know what you're instantiating and what data it can receive.
I'm developing first app using Swift.
in one of my Class I need to store closure in an Array. Like an event manager :
typealias eventCallback = (eventName:String, data:[MasterModel]?) -> Void;
class MHttpLoader: NSObject
{
var eventRegister : [String: [eventCallback]];
class var instance : MHttpLoader
{
struct Static {
static let instance : MHttpLoader = MHttpLoader(baseURL:NSURL(string:"http://192.168.0.11:3000"));
}
return Static.instance;
}
class func registerEvent(eventName:String, callback:eventCallback)
{
if var tab = MHttpLoader.instance.eventRegister[eventName]
{
tab.append(callback);
}
else
{
MHttpLoader.instance.eventRegister[eventName] = [callback];
}
}
func fireEvent(eventName: String, data:[MasterModel]?)
{
if let tab = self.eventRegister[eventName]
{
for callback in tab
{
callback(eventName:eventName, data:data);
}
}
}
}
All this code work pretty well, the problem is when i want to remove a closure from my array.
For exemple :
class func removeEvent(eventName:String, callback:eventCallback)
{
if var tab :Array = MHttpLoader.instance.eventRegister[eventName]
{
if let index = find(tab, callback) as? Int
{
tab.removeAtIndex(index);
}
}
}
I have the error which says that my closure is not conform to protocol "Equatable"
I also tried :
class func removeEvent(eventName:String, callback:eventCallback)
{
if var tab :Array = MHttpLoader.instance.eventRegister[eventName]
{
tab = tab.filter({ (currentElement) -> Bool in
return currentElement != callback;
});
}
}
But I have the error : Cannot invoke '!=' with an argument list of type '((eventCallback), eventCallback)'
Here is my question how can i find the index of closure in array or simply compare closure?
Thank you