【问题标题】:ios - Animated expanding TableView (tap on first row) inside TableViewios - TableView 内的动画展开 TableView(点击第一行)
【发布时间】:2019-04-15 10:05:06
【问题描述】:

我正在构建类似于待办事项应用程序的东西,其中 EXPANDABLE “slave” UITableView 在“master” UITableViewCell 内(原因是“可扩展“slave”表的材料设计)。也许这一切都在容器UIView 内部UIScrollView 内嵌入NavigationViewControllerTabViewController 中也是相关的。相当复杂......让我解释一下:

  • “大师”UITableViewControler 有 2 个部分(今年/长期),带有自定义标题和自定义 TableViewCell

  • 自定义TableViewCell 内部有一个UIView 和“从属”UITableView - 底层UIView 被限制为“从属”UITableView 并使其成为带有阴影的“材质”设计(cropToBounds防止UITableView上的阴影)

  • “Slave”UITableView 应该只有一个扩展部分(我遵循这个家伙的逻辑:https://www.youtube.com/watch?v=ClrSpJ3txAs)– 点击第一行隐藏/显示子视图和页脚

  • “Slave”UITableView 有 3 个自定义 TableViewCell(“header”始终填充在第一行,“subtask”从第二行开始并根据子任务的数量进行填充,“footer”始终在最后一行)

到目前为止丑陋的 UI 的图片可能会更清楚:

Interface Builder setup

UI design

我正在尝试尽可能多地使用 Interface Builder(对于初学者来说,它可以节省大量代码并使事情更加清晰)。

代码明智的架构有点复杂,因为我有一个“目标”领域对象,每次都有一个“子任务”对象列表。因此,“主”UITableViewController 数据源获取一个目标并将其传递给“主”TableViewCell(GoalMainCell)cell.goal = goals?[indexPath.row],即其出口“从”UITableView 的数据源和代表。这样我就可以使用来自领域的正确子任务填充“奴隶”UITableView

当我尝试让两个表的“主”UITableViewController 数据源和委托时,我无法正确填充子任务(即使为每个和一堆 if…else 语句设置 tableView.tag - indexPath.row 也可以' 不被视为目标的索引,因为它从 0 开始每个“奴隶”tableView.row)

类GoalsTableViewController:UITableViewController:(主tableviewcontroller)

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!

class GoalsSlaveTableViewHeaderCell: UITableViewCell(从表的表头——实际上也是一个自定义单元格)

@IBOutlet weak var goalNameLabel: UILabel!
@IBOutlet weak var goalTargetDate: UILabel!
@IBOutlet weak var goalProgressBar: UIProgressView!

类GoalsSlaveTableViewFooterCell:UITableViewCell(和从属的页脚)

@IBAction func deleteGoalButtonTapped(_ sender: UIButton) {
    print("Delete goal")
}

@IBAction func editGoalButtonTapped(_ sender: UIButton) {
    print("Edit goal")
}

问题:如何在展开/折叠后调用带有动画(必要时用于两者)表视图的重新加载数据?

从外观上看,它的工作原理与我想要的差不多。唯一也可能是最棘手的事情是在展开/折叠时自动重新加载/调整“主”单元格和“从属”tableView 大小。换句话说:当我点击“从”表中的第一行时,Realm 中的数据会更新(“扩展”布尔属性),但我必须终止应用程序并再次启动以设置布局(我相信 viewDidLoad 必须跑步)。我只是用几个链接来获得灵感,但我发现 没有 可以解释 如何在运行时通过漂亮的动画在大小方面展开/折叠和调用重新加载

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights Is it possible to implement tableview inside tableview cell in swift 3?

Is it possible to add UITableView within a UITableViewCell

TableView inside tableview cell swift 3

TableView Automatic Dimension Tableview Inside Tableview

Reload a tableView inside viewController

作为一个初学者,我可能会犯一些非常简单的错误,比如在 IB 中缺少一些自动布局约束,因此调用 invalidateIntrinsicContentSize()... “重新加载”的冲突......?希望有人在那里帮助我。感谢您的帮助!

【问题讨论】:

    标签: ios swift uitableview


    【解决方案1】:

    在这种情况下,您可以考虑使用 collectionView。装饰视图可以作为“材料设计”的背景。这也可能会提高加载和滚动性能,因为要加载的表(只有一个集合)会更少,并且您只会在一个类中调用 reload。

    在粗略的收集中,视图需要更多时间来设置。

    【讨论】:

      【解决方案2】:

      我解决了动画/更新问题。

      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()
      
        }
      

      现在更新可以平稳过渡。粗略解决这个问题揭示了索引和主调用自动维度的另一个问题,但这是另一个话题......

      希望它可以帮助其他人减少挣扎。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-21
        • 1970-01-01
        • 1970-01-01
        • 2016-08-25
        • 1970-01-01
        • 1970-01-01
        • 2015-01-27
        相关资源
        最近更新 更多