ios - Анимированное расширение TableView (нажмите на первую строку) внутри TableView - PullRequest
0 голосов
/ 12 ноября 2018

Я создаю что-то вроде приложения 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()… Или, может быть, с этимархитектура не позволяет делать перезагрузки анимированных гладких таблиц, так как всегда будет конфликт "перезагрузок" ...?Надеюсь, есть кто-то, кто поможет мне.Спасибо за любую помощь!

1 Ответ

0 голосов
/ 14 ноября 2018

Я решил анимацию / обновление.

1) Я забыл перезагрузить данные после изменения свойства .expanded:

расширение GoalsMainCell: UITableViewDelegate

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let goalNotificationInfo = ["index" : goalIndex ]
    if indexPath.row == 0 {
        if goal != nil {
            do {
                try realm.write {
                    goal!.expanded = !goal!.expanded
                }
            } catch {
                print("Error saving done status, \(error)")
            }
        }
        loadSubtasks()
        tableView.deselectRow(at: indexPath, animated: true)

        let section = IndexSet.init(integer: indexPath.section)
        tableView.reloadSections(section, with: .none)

        NotificationCenter.default.post(name: .goalNotKey, object: nil, userInfo: goalNotificationInfo as [AnyHashable : Any])
    }



}

2) Я добавил наблюдателя уведомлений в делегат ведомой таблицы и позвонил .beginUpdate() + .endUpdate()

@objc func updateGoalSection(_ notification: Notification) {

    tableView.beginUpdates()

    tableView.endUpdates()

  }

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

Надеюсь, это поможет другим бороться меньше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...