Reputation: 15
I have implemented a tableview with a scrollview inside the cell. The cell consists of two views, the second view (shown in green) appearing after swiping:
What I want to implement now is for the cell height to expand progressively as the cell is swiped to the right. So, as I begin to swipe the cell to the right and the green subview becoming visible on the main screen I want the cell to increase in height until the green subview is completely visible. Then as I swipe to the left, everything gets back to normal.
Since UITableViewCell conforms to the scroll view delegate, I suspect I write some code in my cell subclass - maybe use scrollViewDidScroll using the contentOffset.y
of my scroll view.
Upvotes: 0
Views: 1972
Reputation: 77433
Here is an example (Swift 3) of using auto-layout and auto-sizing cells. Not sure how you plan to "start" with the green view, so this demo initializes it to 40-pts wide (height is contained to 50% of the width).
You can simply create a new .swift file and paste this in, then add a UITableViewController in Storyboard and assign its class to StretchCellTableViewController
. Everything else is created in code... no IBOutlets needed.
//
// StretchCellTableViewController.swift
// SWTemp2
//
// Created by Don Mag on 6/22/17.
// Copyright © 2017 DonMag. All rights reserved.
//
import UIKit
class NonStretchCell: UITableViewCell {
// pretty standard one-label cell
var theLabel: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupCell()
}
func setupCell() {
theLabel = UILabel()
theLabel.font = UIFont.systemFont(ofSize: 16.0, weight: UIFontWeightLight)
theLabel.numberOfLines = 0
theLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(theLabel)
theLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8.0).isActive = true
theLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 6.0).isActive = true
theLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -6.0).isActive = true
theLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8.0).isActive = true
}
}
class StretchCell: UITableViewCell {
var stretchView: UIView!
var widthConstraint: NSLayoutConstraint!
var scaleAction : ((CGFloat)->())?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupCell()
}
func setBarWidth(_ w: CGFloat) -> Void {
widthConstraint.constant = w
}
func setupCell() {
// instantiate the view
stretchView = UIView()
stretchView.translatesAutoresizingMaskIntoConstraints = false
stretchView.backgroundColor = .green
// add it to self (the cell)
self.addSubview(stretchView)
// "pin" it to top, bottom and left
stretchView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0).isActive = true
stretchView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0).isActive = true
stretchView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0).isActive = true
// set height constraint to be 50% of the width
stretchView.heightAnchor.constraint(equalTo: stretchView.widthAnchor, multiplier: 0.5).isActive = true
// instantiate the width constraint
widthConstraint = NSLayoutConstraint(item: stretchView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)
// needs priority of 999 so auto-layout doesn't complain
widthConstraint.priority = 999
// activate the width constraint
NSLayoutConstraint.activate([widthConstraint])
// create a UIPanGestureRecognizer and add it to the stretch view
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
stretchView.addGestureRecognizer(panGesture)
}
func didPan(sender: UIPanGestureRecognizer) {
let loc = sender.location(in: self)
if sender.state == .began {
// if you want to do something on drag start...
//print("Gesture began")
} else if sender.state == .changed {
//print("Gesture is changing", loc)
// update the width of the stretch view (but keep it at least 20-pts wide, so it doesn't disappear)
// height is set to 1:1 so it updates automatically
widthConstraint.constant = max(loc.x, 20)
// call back to the view controller
scaleAction?(widthConstraint.constant)
} else if sender.state == .ended {
// if you want to do something on drag end...
//print("Gesture ended")
}
}
}
class StretchCellTableViewController: UITableViewController {
// array to track the bar widths, assuming we have more than one row with a bar
// only a few rows will have the "stretch bar" - but for this example we'll just track a barWidth for *every* row
var barWidths = [CGFloat]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
tableView.register(StretchCell.self, forCellReuseIdentifier: "StretchCell")
tableView.register(NonStretchCell.self, forCellReuseIdentifier: "NonStretchCell")
// init array for barWidths... initial value: 40, 30 slots
barWidths = Array(repeating: 40, count: 30)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return barWidths.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// just make every 5th row a "Stretch" cell (starting at #3)
if indexPath.row % 5 == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "StretchCell", for: indexPath) as! StretchCell
cell.selectionStyle = .none
// cells are reused, so set the bar width to our saved value
cell.setBarWidth(barWidths[indexPath.row])
// "call-back" function
cell.scaleAction = {
(newWidth) in
// update the bar width in our tracking array
self.barWidths[indexPath.row] = newWidth
// disable Animations, because we're changing the size repeatedly
UIView.setAnimationsEnabled(false)
// wrap begin/end updates() to force row-height re-calc
self.tableView.beginUpdates()
self.tableView.endUpdates()
// re-enable Animations
UIView.setAnimationsEnabled(true)
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "NonStretchCell", for: indexPath) as! NonStretchCell
// just for demonstration's sake
if indexPath.row % 7 == 5 {
cell.theLabel.text = "Row: \(indexPath.row) - This is just to show the text wrapping onto multiple lines. Pretty standard for auto-sizing UITableView cells."
} else {
cell.theLabel.text = "Row: \(indexPath.row)"
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
}
Result is:
Upvotes: 5
Reputation: 1300
As DonMag suggested you should trigger the height change by calling beginUpdates and endUpdates. Although not quite the same thing I implemented something very similar in this answer: how to change the height of a single cell when clicked?
I am sure you can easily adapt it to your needs.
Upvotes: 0