Swift: рекурсивно циклически просматривайте все подпункты, чтобы найти определенный класс и добавить к массиву

Имея дьявола времени, пытаясь понять это. Я задал аналогичный вопрос здесь: Swift: получить все подвидности определенного типа и добавить в массив

В то время как это работает, я понял, что существует много подзапросов и под-sub-представлений, поэтому мне нужна функция, которая начинается с основного UIView, циклически проходит через все подзоны (и их подпункты до тех пор, пока их не осталось) и добавляет его в массив для пользовательского класса кнопок, который я назвал CheckCircle.

По сути, я хотел бы получить массив CheckCircles, который будет содержать все CheckCircles, добавленные к этому виду программно.

Любые идеи? Вот о чем я работаю. Кажется, это не добавляет Checkcircles в массив:

    func getSubviewsOfView(v:UIView) -> [CheckCircle] {
        var circleArray = [CheckCircle]()
        // Get the subviews of the view

        var subviews = v.subviews

        if subviews.count == 0 {
            return circleArray
        }

        for subview : AnyObject in subviews{
  if let viewToAppend = subview as? CheckCircle {
        circleArray.append(viewToAppend as CheckCircle)
      }
            getSubviewsOfView(subview as! UIView)
        }
        return circleArray
    }

Ответ 1

Основная проблема заключается в том, что когда вы вызываете getSubviewsOfView(subview as! UIView) (рекурсивно, внутри функции), вы ничего не делаете с результатом.

Вы также можете удалить проверку count == 0, так как в этом случае цикл for…in будет просто пропущен. У вас также есть куча ненужных бросков

Предполагая, что ваше желание состоит в том, чтобы получить плоский массив экземпляров CheckCircle, я думаю, что эта адаптация вашего кода должна работать:

func getSubviewsOfView(v:UIView) -> [CheckCircle] {
    var circleArray = [CheckCircle]()

    for subview in v.subviews as! [UIView] {
        circleArray += getSubviewsOfView(subview)

        if subview is CheckCircle {
            circleArray.append(subview as! CheckCircle)
        }
    }

    return circleArray
}

Ответ 2

Based on Aaron Brager и ullstrm answers

Подробнее

  • Xcode 9.1, Swift 4,
  • Версия Xcode 10.3 (10G8), Swift 5

Решение

extension UIView {

    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        return parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }

    class func getAllSubviews(from parenView: UIView, types: [UIView.Type]) -> [UIView] {
        return parenView.subviews.flatMap { subView -> [UIView] in
            var result = getAllSubviews(from: subView) as [UIView]
            for type in types {
                if subView.classForCoder == type {
                    result.append(subView)
                    return result
                }
            }
            return result
        }
    }

    func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get<T: UIView>(all type: T.Type) -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get(all types: [UIView.Type]) -> [UIView] { return UIView.getAllSubviews(from: self, types: types) }
}

Пример использования

var allViews = UIView.getAllSubviews(from: simpleView)
func printResult(with text: String) {
    print("\n==============================================")
    print("\(text):\n\(allViews.map { $0.classForCoder } )")
}
printResult(with: "UIView.getAllSubviews(from: simpleView)")

allViews = UIView.getAllSubviews(from: simpleView) as [UILabel]
printResult(with: "UIView.getAllSubviews(from: simpleView) as [UILabel]")

allViews = UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])
printResult(with: "UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])")

allViews = simpleView.getAllSubviews()
printResult(with: "simpleView.getAllSubviews()")

allViews = simpleView.getAllSubviews() as [UILabel]
printResult(with: "simpleView.getAllSubviews() as [UILabel]")

allViews = simpleView.get(all: UILabel.self)
printResult(with: "simpleView.get(all: UILabel.self)")

allViews = simpleView.get(all: [UIStackView.self, UILabel.self])
printResult(with: "simpleView.get(all: [UIStackView.self, UILabel.self])")

Вывод образца

==============================================
UIView.getAllSubviews(from: simpleView):
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
UIView.getAllSubviews(from: simpleView) as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews():
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews() as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: UILabel.self):
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

Раскадровка образца

enter image description here

Другая информация

Также предлагаю работать со слабыми ссылками. Массив со слабыми ссылками на объекты

Ответ 3

Мой подход с быстрыми 3 и дженериками!

private func getSubviewsOf<T: UIView>(view: UIView) -> [T] {
    var subviews = [T]()

    for subview in view.subviews {
        subviews += getSubviewsOf(view: subview) as [T]

        if let subview = subview as? T {
            subviews.append(subview)
        }
    }

    return subviews
}

Чтобы получить все UILabel в иерархии представлений, просто выполните следующее:

let allLabels: [UILabel] = getSubviewsOf(view: theView)

Ответ 4

Вы можете реализовать это, просто расширив UIView и определив следующие функции.

Код Swift4

extension UIView {
    func findViews<T: UIView>(subclassOf: T.Type) -> [T] {
        return recursiveSubviews.compactMap { $0 as? T }
    }

    var recursiveSubviews: [UIView] {
        return subviews + subviews.flatMap { $0.recursiveSubviews }
    }
}

использование

findViews(subclassOf: UILabel.self)
findViews(subclassOf: CheckCircle.self)

Ответ 5

По материалам ответов Василия Боднарчука, Аарона Брагера и Ульстрема.

Почему еще один?

Лично я не хотел бы иметь as [XXX] и let specific: [Type], все, но скорее передать тип в вызовы функций, например

let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

Я также переименовал All в Nested потому что он лучше передает рекурсивную природу функции вызывающей стороне API.

подробности

Swift 4.x, Xcode 9. 1+

Решение

extension UIView {

    class func getNestedSubviews<T: UIView>(view: UIView) -> [T] {
        return view.subviews.flatMap { subView -> [T] in
            var result = getNestedSubviews(view: subView) as [T]
            if let view = subView as? T {
                result.append(view)
            }
            return result
        }
    }

    func getNestedSubviews<T: UIView>() -> [T] {
        return UIView.getNestedSubviews(view: self) as [T]
    }
}

использование

let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

Ответ 6

UITextField больше не находится на верхнем слое подпредставлений, поэтому я использую этот метод:

@implementation UISearchBar (changeFont)

- (void)setFont:(UIFont *)font {
    for (UIView *v in [self subviews]) {
        if ([v isKindOfClass:[UITextField class]]) {
            UITextField *tf = (UITextField *)v;
            tf.font = font;
            UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
            l.font = font;
            break;
        } else if (v.subviews.count) {
            for (UIView *v1 in v.subviews) {
                if ([v1 isKindOfClass:[UITextField class]]) {
                    UITextField *tf = (UITextField *)v1;
                    tf.font = font;
                    UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                    l.font = font;
                    break;
                } else if (v1.subviews.count) {
                    for (UIView *v2 in v1.subviews) {
                        if ([v2 isKindOfClass:[UITextField class]]) {
                            UITextField *tf = (UITextField *)v2;
                            tf.font = font;
                            UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                            l.font = font;
                            break;
                        }
                    }
                }
            }
        }
    }
}

немного затянуто, но следует учитывать, что текстовое поле будет двигаться глубже в будущем