@IBInspectable для установки Typealias для класса

Я пытаюсь построить общий UITableViewController с Realm Results<Object> в качестве модели.

Это мои упрощенные классы:

Объект области:

import RealmSwift

class Test: Object {

  dynamic var name = ""

}

ТаблицаViewCell:

import UIKit
import RealmSwift

class RealmCell: UITableViewCell {
  typealias Entity = Test // from above

  var object: Entity? {
    didSet {
      if let object = object {
        textLabel?.text = object.name
      }
    }
  }

}

Таблевиевконтроллер:

import UIKit
import RealmSwift

class RealmTableViewController: UITableViewController {

  typealias TableCell = RealmCell // From example above            
  var objects = try! Realm().objects(TableCell.Entity.self) {
    didSet { tableView.reloadData() }
  }

  // MARK: - UITableViewDataSource

  override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
  }

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return objects.count
  }

  override func tableView(_ tableView: UITableView,
                          cellForRowAt indexPath: IndexPath)
    -> UITableViewCell {

      let cell =
        tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableCell

      cell.object = objects[indexPath.row]

      return cell
  }
}

Я не могу придумать способ сделать псевдоним типа TableCell @IBInspectable. Я безуспешно пытался использовать NSClassFromString(_:).

Надеюсь, кто-то может помочь.


person pesch    schedule 11.05.2017    source источник
comment
не могу получить ваш вопрос? В чем дело. Зачем вы создали RealmCell, если не используете в TableView?   -  person Gagan_iOS    schedule 11.05.2017
comment
Я не могу создать подкласс для этого RealmTableViewController, поэтому я вынужден повторить свой код, заменив псевдоним типа. Я хотел бы иметь общий класс, который можно определить в InterfaceBuilder, набрав в @IBInspectable EntityName   -  person pesch    schedule 11.05.2017


Ответы (1)


Если я вас правильно понял, вы в основном хотите указать имя объекта в Interface Builder, да? т.е. вы хотите иметь возможность в основном выбирать свой пользовательский класс из инспектора?

Если это так, то это, к сожалению, невозможно напрямую. @IBInspectable можно использовать только с определенными типами, как задокументировано здесь:

Вы можете добавить атрибут IBInspectable к любому свойству в объявлении класса, расширении класса или категории типа: логическое значение, целое число или число с плавающей запятой, строка, локализованная строка, прямоугольник, точка, размер, цвет, диапазон и ноль.

Однако вы можете указать строковое свойство как IBInspectable (с значимым значением по умолчанию), и в ваших инициализаторах вычесть из него класс. Это оставит открытой возможность ошибки, то есть опечатки, но все же может работать.

См. также этот ответ.

Редактировать после вашего комментария: В этом случае я боюсь, что это будет невозможно (по крайней мере, насколько я знаю, может быть какой-то хакерский прием глубокого уровня, о котором я не знаю, но это скорее всего будет некрасиво). Проблема в том, что то, что указано в IB, может быть оценено только во время выполнения, а typealias определяется во время компиляции.

Я думаю, что в основном вам нужен просто протокол для функций, которые должен иметь каждый класс ячеек (и на который опирается общий контроллер представления). Тогда вам даже не понадобится typealias, поскольку протокол сам по себе является типом.

Затем можно выбрать конкретные классы ячеек на основе строки IBInspectable, протокол может даже определить для этого метод.

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

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


Второе редактирование после просмотра вашего примера кода:

Хорошо, я посмотрел на это еще раз и, надеюсь, теперь могу объяснить это немного лучше. К сожалению, я не могу просто добавить в ваш репозиторий напрямую, так как он не компилируется (мне не хватает фреймворка/модуля Realm, и даже если бы я добавил, что, вероятно, ничего не выиграл бы, потому что я не знаю, что именно вы с ним делаете ).

Как сказано в комментарии, я бы сказал, что вам не нужно дополнительное свойство IBInspectable для установки класса. Это уже должно произойти в вашей раскадровке, т. е. вы должны установить значение класса данной ячейки-прототипа на один из конкретных классов ячеек, которые у вас есть. Однако вашему универсальному RealmTableViewController не нужно знать этот класс. Вы, кажется, хотите дать ему знание о том, что, если я правильно вас понимаю, вероятно, чтобы он правильно подготовил ячейку в зависимости от ее конкретных характеристик. Не делайте этого (сейчас я перейду к тому, что вы хотите сделать в viewDidLoad). Вместо этого определить протокол, который принимают все ячейки и о котором знает RealmTableViewController. В вашем методе tableView(_:cellForRowAt:) вы используете этот протокол в части as! ... при удалении ячейки из очереди. Протокол должен определять метод подготовки, который должен быть реализован каждым конкретным классом и который затем вызывается в этот момент RealmTableViewController.

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

Теперь к проблеме, с которой вы (я думаю) сталкиваетесь: вы хотите, чтобы контроллер знал, какой тип ячейки-прототипа он использует, чтобы он тоже мог подготовить определенные вещи (которые вы также можете передать в протокол, например). Это проблематично, потому что вы в основном пытаетесь восстановить основной аспект контроллера табличного представления: способность обрабатывать разные типы ячеек одновременно. Я заключаю это из вашего другого кода, в viewDidLoad и свойстве objects, которое в конечном итоге, похоже, также зависит от класса ячейки. Это противоречит дизайну самой табличной архитектуры, это не просто синтаксическая проблема. Однако есть решение: другой протокол плюс класс-компаньон для конкретных классов ячеек.

Для каждого класса ячеек, который у вас есть, определите соответствующий класс, который имеет дело с вещами Realm, которые должны выполнять ваши контроллеры представления. Может быть, для некоторых ячеек у вас есть один и тот же объект, тогда напишите их соответствующим образом (у нового класса должно быть свойство, определяющее, какой ячейке он соответствует). Определите протокол, который они все принимают и который имеет по крайней мере два метода (можно использовать лучшие имена, здесь уже поздно...): doControllerStuff и getCellIdentifier.

Тогда ваш RealmTableViewController, наконец, действительно получает IBInspectable. Если это установлено, контроллер создает объект-компаньон в ячейке (какой конкретный класс используется, очевидно, будет зависеть от значения IBInspectable). Если один и тот же компаньон обрабатывает несколько ячеек, IBInspectable также должен определить, какая ячейка будет использоваться, т. е. компаньон должен быть настроен правильно. Вы можете использовать какое-то строковое соглашение или даже написать фабричный класс, который скрывает конкретный класс от контроллера представления и просто возвращает ему правильный объект, типизированный как протокол.

В любом случае, что бы вы ни делали в viewDidLoad на основе класса, измените это на метод doControllerStuff. У вас может быть даже несколько супер- и подклассов, если это может быть общим для ячеек/компаньонов, это не имеет значения. Наконец, в вашем методе `tableView(_:cellForRowAt:) вы не используете идентификатор напрямую, а вместо этого запрашиваете идентификатор у объекта-компаньона.

Однако здесь есть небольшое предостережение: вы, очевидно, должны убедиться, что значение построителя интерфейса для идентификатора ячейки установлено правильно, то есть оно соответствует IBInspectable, который вы установили в экземпляре контроллера представления. Вы можете написать об этом ловушку и использовать ячейку по умолчанию, или просто выдать исключение.


Это стало довольно длинным, и я надеюсь, что это понятно, несмотря на это. У меня нет времени, чтобы сделать красивую графику или что-то в этом роде, поэтому, если у вас все еще есть вопросы, я предлагаю обсудить это в чате. :) Просто пингуйте меня, и мы это сделаем (хотя у меня немного свободного времени, начиная с завтрашнего дня).

person Gero    schedule 15.05.2017
comment
Не имя сущности, а класс для RealmCell. В настоящее время я вынужден дублировать код. По одному для каждого RealmEntity: RealmEntity1Cell с RealmEntity1TableViewController для Entity1 и так далее. Я хочу создать определенные RealmEntityCells и указать их имя как String в InterfaceBuilder для общего RealmTableViewController. Я понимаю, что мне нужно будет указать имя класса RealmCell в виде строки в InterfaceBuilder. Однако я не могу назначить псевдоним типа, используя класс, созданный из предоставленного className. - person pesch; 15.05.2017
comment
Большое спасибо @Gero. Это именно то, что я ищу. Но часть, с которой я застрял, - это часть вашего абзаца. Затем можно выбрать конкретные классы ячеек на основе строки IBInspectable, протокол может даже определить метод для этого. Буду признателен, если вы взглянете на этот код github.com/epeschard/RealmGenerics - person pesch; 16.05.2017
comment
@pesch Я на работе, поэтому не могу вдаваться в подробности, но сегодня вечером постараюсь взглянуть на ваш код. А пока позвольте мне сказать следующее: если вы удаляете ячейки из очереди, как обычные ячейки-прототипы, вам, очевидно, придется предоставить правильный, конкретный тип ячейки через IB, который в некотором смысле будет выбираться на основе IBInspectable, учитывая, что идентификатор в основном эквивалентен. Ваша раскадровка/xib должна иметь конкретный класс в качестве типа для ячейки, очевидно. Как уже упоминалось, я объясню это более подробно сегодня вечером. :) - person Gero; 16.05.2017
comment
Большое спасибо за Вашу помощь. Я уже использовал протоколы, но не мог использовать протокол как тип, поскольку у него был связанный тип. Вот почему я пытался определить класс, соответствующий протоколу в InterfaceBuilder. Все началось с использования ассоциированного типа для этой строки: var objects = try! Realm().objects(TableCell.Entity.self) { didSet { tableView.reloadData() } } Это было удобно, но его можно переместить, например, в prepareForSegue. - person pesch; 17.05.2017
comment
Я рад, что смог помочь. Просто для полноты: вы все равно можете использовать протокол в качестве типа, даже если он имеет связанный тип. В вашем случае это не сработало, потому что вы пытались использовать конкретную часть соответствующего объекта, где вы не могли знать, какой это конкретный тип. Это просто оказалось связанным с соответствующим типом. :) Перемещение, например. prepareForSegue решает эту проблему, и это хорошая идея. Удачного кодирования. :) - person Gero; 17.05.2017