Я создаю что-то вроде приложения todo, в котором у меня есть РАСШИРЯЕМЫЙ «ведомый» UITableView
внутри «главного» * 1004 * (причина - «материальный дизайн расширяемой« ведомой »таблицы). Возможно также уместно, что это все внутри контейнера UIView
внутри UIScrollView
, встроенного в NavigationViewController
и TabViewController
. Довольно сложный ... Позвольте мне объяснить:
«Мастер» UITableViewControler
с 2 секциями (в этом году / на длительный срок) с пользовательскими заголовками и пользовательскими TableViewCell
Пользовательский TableViewCell
имеет UIView
и «подчиненный» UITableView
внутри - базовый UIView
ограничен «подчиненным» UITableView
и делает его «материальным» дизайном с тенью (cropToBounds
предотвращает тень на UITableView
)
«Ведомый» UITableView
должен быть только с одним расширяющимся разделом (я следовал логике этого парня: https://www.youtube.com/watch?v=ClrSpJ3txAs) - нажмите на скрытие первого ряда / показать подпредставления и нижний колонтитул
«Ведомый» UITableView
имеет 3 пользовательских TableViewCell
(«заголовок» заполняется всегда в первой строке, «подзадача» начинается со второй и заполняется в зависимости от количества подзадач, «нижний колонтитул» всегда последний)
Изображение такого ужасного пользовательского интерфейса может сделать его более понятным:
Настройка Interface Builder
Дизайн пользовательского интерфейса
Я стараюсь максимально использовать Interface Builder (для меня, как для новичка, это экономит много кода и делает вещи более понятными).
С точки зрения кода, это немного сложная архитектура, так как у меня есть объект области «Цель», который каждый раз имеет список объектов «Подзадача». Таким образом, «master» UITableViewController
dataSource захватывает цель и передает ее «master's» TableViewCell
(GoalMainCell) cell.goal = goals?[indexPath.row]
, который является dataSource и Delegate для его выхода «slave» UITableView
. Таким образом, я могу заполнить «раб» UITableView
его правильными подзадачами из области.
Когда я попытался создать «master» UITableViewController
в качестве источника данных и делегата обеих таблиц, я не смог правильно заполнить подзадачи (даже установив tableView.tag
для каждой и кучу операторов if…else
- indexPath.row
можно не приниматься за индекс цели, поскольку он начинается с 0 для каждого «раба» tableView.row
)
класс GoalsTableViewController: UITableViewController: (главный контроллер tableview)
let realm = try! Realm()
var goals: Results<Goal>?
var parentVc : OurTasksViewController?
var numberOfSubtasks: Int?
let longTermGoalsCount = 0
@IBOutlet var mainTableView: UITableView!
@IBOutlet weak var noGoalsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loadGoals()
}
override func viewDidAppear(_ animated: Bool) { // MUST be viewDidAppear
super.viewDidAppear(true)
parentVc = self.parent as? OurTasksViewController
}
func loadGoals() {
goals = realm.objects(Goal.self)
if goals?.count == 0 || goals?.count == nil { noGoalsLabel.isHidden = false }
tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
if longTermGoalsCount > 0 {
//TODO: split goals to "this year" and "future" and show second section if future has some goals
return 2
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalMainCell", for: indexPath) as! GoalsMainCell
cell.goal = goals?[indexPath.row] //send goal in to the GoalMainCell controller
cell.layoutIfNeeded()
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ThisYearGoalsHeaderTableViewCell") as! ThisYearGoalsHeaderTableViewCell
cell.layoutIfNeeded()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "LongTermGoalsHeaderCell")!
cell.layoutIfNeeded()
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 44
}
класс GoalsMainCell: UITableViewCell (пользовательская ячейка основной таблицы)
@IBOutlet weak var goalsSlaveTableView: GoalsSlaveTableView!
let realm = try! Realm()
var subtasks: Results<GoalSubtask>?
var numberOfRows: Int = 0
var goal: Goal? { //goal passed from master tableView
didSet {
loadSubtasks()
}
}
func loadSubtasks() {
subtasks = goal?.subtasks.sorted(byKeyPath: "targetDate", ascending: true)
guard let expanded = goal?.expanded else {
numberOfRows = 1
return
}
if expanded {
numberOfRows = (subtasks?.count ?? 0) + 2
} else {
numberOfRows = 1
}
}
расширение GoalsMainCell: UITableViewDataSource (пользовательская ячейка основной таблицы)
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalsSlaveTableViewHeaderCell", for: indexPath) as! GoalsSlaveTableViewHeaderCell
cell.goalNameLabel.text = goal?.name ?? "No task added"
cell.layoutIfNeeded()
return cell
} else if indexPath.row > 0 && indexPath.row < numberOfRows - 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalsSlaveTableViewCell", for: indexPath) as! GoalsSlaveTableViewCell
cell.subTaskNameLabel.text = subtasks?[indexPath.row - 1].name //because first row is main goal name
cell.layoutIfNeeded()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalsSlaveTableViewFooterCell", for: indexPath) as! GoalsSlaveTableViewFooterCell
cell.layoutIfNeeded()
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 0
}
расширение GoalsMainCell: UITableViewDelegate (пользовательская ячейка основной таблицы)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if goal != nil {
do {
try realm.write {
goal!.expanded = !goal!.expanded
}
} catch {
print("Error saving done status, \(error)")
}
}
}
let section = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(section, with: .none)
tableView.deselectRow(at: indexPath, animated: true)
}
класс GoalsSlaveTableView: UITableView (ведомый tableViewController)
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = cornerRadius
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
override var contentSize: CGSize {
didSet{
self.invalidateIntrinsicContentSize()
}
}
класс GoalsSlaveTableViewCell: UITableViewCell (ячейка ведомой таблицы)
@IBOutlet weak var subTaskNameLabel: UILabel!
@IBOutlet weak var subTaskTargetDate: UILabel!
@IBOutlet weak var subTaskDoneImage: UIImageView!
класс GoalsSlaveTableViewHeaderCell: UITableViewCell (заголовок ведомой таблицы - фактически также пользовательская ячейка)
@IBOutlet weak var goalNameLabel: UILabel!
@IBOutlet weak var goalTargetDate: UILabel!
@IBOutlet weak var goalProgressBar: UIProgressView!
class GoalsSlaveTableViewFooterCell: UITableViewCell (и нижний колонтитул для ведомого)
@IBAction func deleteGoalButtonTapped(_ sender: UIButton) {
print("Delete goal")
}
@IBAction func editGoalButtonTapped(_ sender: UIButton) {
print("Edit goal")
}
Вопрос: Как вызвать перезагрузку данных с анимацией (для обоих при необходимости) табличных представлений после развертывания / свертывания?
У меня все получилось так, как я хочу по внешнему виду. Единственное и, вероятно, самое сложное, чего не хватает - это автоматическая перезагрузка / настройка «основной» ячейки и «ведомого» размера tableView при расширении / свертывании. Другими словами: когда я нажимаю на первую строку в «ведомой» таблице, данные в Realm обновляются («расширенное» свойство bool), но мне нужно завершить приложение и запустить снова, чтобы настроить макет (я считаю, что viewDidLoad должен бежать). Просто несколько ссылок, которые я использовал для вдохновения, но я нашел нет , который объяснил бы , как расширить / свернуть и вызвать перезагрузку обоих с точки зрения размера с хорошей анимацией во время выполнения :
Использование автоматического размещения в UITableView для динамического размещения ячеек и переменной высоты строк
Возможно ли реализовать просмотр таблицы внутри ячейки просмотра таблицы в swift 3?
Это посМожно добавить UITableView в UITableViewCell
TableView внутри ячейки табличного просмотра swift 3
TableView Автоматическое измерение Tableview Inside Tableview
Перезагрузить tableView внутри viewController
Как новичок, я мог бы сделать действительно простую ошибку, например, пропустить некоторые ограничения автоматического размещения в IB и, следовательно, вызвать invalidateIntrinsicContentSize()
… Или, может быть, с этимархитектура не позволяет делать перезагрузки анимированных гладких таблиц, так как всегда будет конфликт "перезагрузок" ...?Надеюсь, есть кто-то, кто поможет мне.Спасибо за любую помощь!