ios-TableView内部的动画扩展TableView(在第一行上点击)

时间:2018-11-12 16:28:48

标签: ios swift uitableview

我正在构建类似待办事项应用程序的东西,其中我在 UITableView中包含了 EXPANDABLE “ slave” UITableViewCell(原因是“ expandable” slave的“材料设计” ”表)。也可能与此有关,它们都位于UIViewUIScrollView中嵌入的NavigationViewController内的容器TabViewController中。非常复杂...让我解释一下:

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

  • 自定义TableViewCell在内部具有UIView和“从属” UITableView-基础UIView被限制为“从属” UITableView并使其带有阴影的“材料”设计(cropToBounds可以防止UITableView上的阴影)

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

  • “从属” UITableView具有3个自定义TableViewCell(“标头”始终填充在第一行,“子任务”始终填充在第二行,并根据子任务的数量填充,“页脚”始终最后)

到目前为止,丑陋的UI图片可能会使其更加清晰:

Interface Builder setup

UI design

我正在尝试尽可能多地使用Interface Builder(对我来说,作为一个初学者,它节省了大量代码,并使事情变得更清楚)。

从代码的角度来看,它有点复杂,因为我有一个“目标”领域对象,每次都有“子任务”对象列表。因此,“主” UITableViewController数据源抓住了一个目标,并将其传递给了“主” TableViewCell(GoalMainCell)cell.goal = goals?[indexPath.row],它是dataSource并为其出口“奴隶” UITableView委托。这样,我可以使用领域中正确的子任务填充“从属” UITableView

当我试图让“主” UITableViewController成为两个表的数据源和委托时,我无法正确填充子任务(即使为tableView.tag的每一行都设置if…else语句– indexPath.row不能作为目标的索引,因为对于每个“从属” tableView.row)它从0开始。

类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!

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的大小。换句话说:当我点击“ slave”表中的第一行时,Realm中的数据将被更新(“ expanded” bool属性),但是我必须终止应用程序并再次启动以设置布局(我认为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()……或者也许使用这种体系结构不可能进行动画平滑表重装,因为这样会总是“重装”冲突...?希望那里有人可以帮助我。谢谢您的帮助!

2 个答案:

答案 0 :(得分:0)

我解决了动画/更新。

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

  }

现在,更新可以平滑过渡了。在粗略解决方案中,这揭示了索引编制和主调用自动尺寸的另一个问题,但这是另一个主题...

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

答案 1 :(得分:0)

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

在粗略收集中,视图需要花费更多的时间来建立。