I made an animation that moves an orange View when a button is pressed.
blue = UIButton, orange = animation target View
for button in toggleButtons {
button.addTarget(self, action: #selector(toggleButtonAction), for: .touchUpInside)
}
I created 3 buttons and wrote logic to move them to the position of the buttons, but the orange view moves, but no animation is applied.
#objc func toggleButtonAction(_ sender: UIButton) {
self.toggleLeading.constant = sender.frame.width * CGFloat(sender.tag)
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseIn, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
The code above is my animation code. It works fine in other UIViewControllers, but it doesn't work in the Custom TableviewCell.
When the button is pressed, the orangeView moves, but it moves like a teleportation rather than an animation.
I think we need more code to see why is not working for you but I quickly tried this on a playground and seems to be working fine:
import PlaygroundSupport
import UIKit
class MyCell: UITableViewCell {
private var leadingConst: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let view = UILabel()
view.text = "Move view"
view.backgroundColor = .orange
view.translatesAutoresizingMaskIntoConstraints = false
let buttons: [UIButton] = (0...2).map {
let button = UIButton()
button.backgroundColor = .blue
button.tag = $0
button.setTitle("\($0 + 1)", for: .normal)
button.addTarget(self, action: #selector(onTap), for: .touchUpInside)
return button
}
let stackView = UIStackView(arrangedSubviews: buttons)
stackView.distribution = .fillEqually
stackView.axis = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
contentView.addSubview(view)
leadingConst = view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0)
NSLayoutConstraint.activate([
leadingConst,
view.heightAnchor.constraint(equalToConstant: 25),
view.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 1 / 3),
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
required init?(coder: NSCoder) {
nil
}
#objc func onTap(_ sender: UIButton) {
self.leadingConst.constant = sender.frame.width * CGFloat(sender.tag)
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseIn, animations: {
self.contentView.layoutIfNeeded()
}, completion: nil)
}
}
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
50
}
override func numberOfSections(in tableView: UITableView) -> Int {
1
}
}
PlaygroundPage.current.liveView = ViewController()
Related
Hello what I have is a UITableViewController which displays various sliding out menu options. The idea I have is instead of the menu options to be displayed right below the navigation bar but rather include an image and have these items displayed below the image. I tried to anchor the UITable view to the bottom anchor of the image but it does not work.
Below are some images:
class MenuViewController: UITableViewController {
public var delegate: MenuControllerDelagate?
private let menuItems: [MenuOptions]
let darkColour = UIColor(displayP3Red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1)
private let profileImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.isUserInteractionEnabled = true
iv.image = #imageLiteral(resourceName: "venom-7")
return iv
}()
init(with menuItems: [MenuOptions]) {
self.menuItems = menuItems
super.init(nibName: nil, bundle: nil)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profileImageView)
profileImageView.anchor(top: view.topAnchor)
profileImageView.setDimensions(height: 130, width: 130)
profileImageView.layer.cornerRadius = 130/2
profileImageView.centerX(inView: view)
tableView.backgroundColor = darkColour
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.textColor = .white
cell.textLabel?.text = menuItems[indexPath.row].rawValue
cell.backgroundColor = darkColour
cell.imageView?.image = UIImage(systemName: MenuOptions.allCases[indexPath.row].imageName)
cell.imageView?.tintColor = .white
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true )
let selectedItem = menuItems[indexPath.row]
delegate?.didSelectMenuItem(named: selectedItem)
}
}
Any advice how I could properly anchor the list items to be below the image would be greatly appreciated!
It seems like you're using UITableViewController and it doesn't explicitly add subviews as UIViewCroller. Your must conform ViewController to UIViewController.
After that initialize your UITableView
lazy var tableView: UITableView {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}
and in viewDidLoad() write
view.addSubview(tableView)
then create a function
func setupViews() {
NSLayoutConstraint.activate([
profileImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
profileImageView.heightAnchor.constraint(equalToConstant: 100),
profileImageView.widthAnchor.constraint(equalTo: profileImageView.heightAnchor, multiplier: 1.0),
profileImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tableView.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20),
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
and call the setupViews() in viewDidLoad
From what I understand, instead of using UITableviewController you should use a UIViewController and add a UITableView in it then set the constraints.
class MenuViewController: UITableViewController
Instead use
class MenuViewController: UIViewController {
private lazy var tableView: UITableView = {
...
}
}
Set the constraints now and subview. I think that would solve the issue
I have a UITableView, and in each of the UITableViewCells, I have a button, which should perform an action when selected. If the user decides the select the actual cell, I handle another action within the didSelectRowAt function. I am not sure if I am missing something, but my button does not work when selected, but performs the didSelectRowAt function.
The layout and constraints work just fine. It is just not recognizing the tap, and then pushes to the didSelectRowAt
UIViewController Code (Clipped)
class MyProfile: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView : UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor.white
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 10).isActive = true
tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 10).isActive = true
view.backgroundColor = UIColor.white
tableView.delegate = self
tableView.dataSource = self
tableView.register(ProfileCell.self, forCellReuseIdentifier: "profileCell")
// data and backend stuff to reload tableview has been removed, so there is not too much code
// Do any additional setup after loading the view.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.myServices.text = "My Services"
return myJobs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "profileCell", for: indexPath) as! ProfileCell
cell.selectionStyle = .none
cell.deleteButton.addTarget(self, action: #selector(deletePostPressed), for: .touchUpInside)
cell.editButton.addTarget(self, action: #selector(editPostPressed), for: .touchUpInside)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.navigationController?.pushViewController(ViewPostController(), animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 175
}
#objc func deletePostPressed() {
print("delete")
}
#objc func editPostPressed() {
print("edit")
self.present(Home(), animated: true, completion: nil)
}
}
UITableViewCell Code (Clipped)
class ProfileCell: UITableViewCell {
let editButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Edit", for: .normal)
button.setTitleColor(UIColor.mainBlue, for: .normal)
button.backgroundColor = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
button.isUserInteractionEnabled = true
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.mainBlue.cgColor
return button
}()
let deleteButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Delete", for: .normal)
button.setTitleColor(UIColor.mainBlue, for: .normal)
button.backgroundColor = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
button.isUserInteractionEnabled = true
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.mainBlue.cgColor
return button
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.selectionStyle = .none
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
isUserInteractionEnabled = true
addSubview(editButton)
editButton.layer.cornerRadius = 15
editButton.layer.masksToBounds = true
editButton.topAnchor.constraint(equalTo: saleNumber.bottomAnchor, constant: 7).isActive = true
editButton.rightAnchor.constraint(equalTo: informationView.rightAnchor, constant: -15).isActive = true
editButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
editButton.widthAnchor.constraint(equalToConstant: 85).isActive = true
addSubview(deleteButton)
deleteButton.layer.cornerRadius = 15
deleteButton.layer.masksToBounds = true
deleteButton.topAnchor.constraint(equalTo: saleNumber.bottomAnchor, constant: 7).isActive = true
deleteButton.rightAnchor.constraint(equalTo: editButton.leftAnchor, constant: -15).isActive = true
deleteButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
deleteButton.widthAnchor.constraint(equalToConstant: 85).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am not sure what else could be the problem. I have declared delegates, corrected everything, and made sure everything else that needed to be initialized was, and still it doesn't work.
It's a matter of hierarchy, you need to add the buttons to the contentView. If you just add a subview to the view, it goes behind the contentView of the UITableViewCell.
Visually, where the selected one is the contentView of the cell, you can see that the actual button is behind it:
So contentView.addSubview(editButton) and contentView.addSubview(deleteButton) does the job.
I am trying to create a UITableView that has a hidden subview at the bottom that will slide open when the cell is tapped. I have the following demo code:
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
tableView.separatorStyle = .none
tableView.register(ExpandTableCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
return cell ?? UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandTableCell else { return }
tableView.performBatchUpdates({ cell.animate() }, completion: nil)
}
}
And the cell:
class ExpandTableCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
setupViews()
}
private let blueView = UIView()
// MARK: - Views
private func setupViews() {
selectionStyle = . none
let titleLabel = UILabel()
titleLabel.text = "Some Title"
let subtitleLabel = UILabel()
subtitleLabel.text = "Some othere sdfhdslkjl dsfljdslfj sdlj sdfldsjfldsjf sdfjdslfjds"
subtitleLabel.numberOfLines = 2
blueView.backgroundColor = .blue
blueView.translatesAutoresizingMaskIntoConstraints = false
blueView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel, blueView])
stackView.axis = .vertical
stackView.spacing = 8.0
blueView.isHidden = true
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
func animate() {
UIView.animate(withDuration: 0.1, animations: { [blueView] in
blueView.isHidden.toggle()
})
}
}
The problem is that the animation has the following effect:
It's squashing the contents of the label above it. It should just slide down from the bottom.
What am I doing wrong here?
Just change the animation timing to match that of the tableView. Try 0.3
func animate() {
UIView.animate(withDuration: 0.3, animations: { [blueView] in
blueView.isHidden.toggle()
})
}
The artifact is gone.
I have an UIImageView inside my table view cell, named pic. I want to use an array named colors (filled with UIImages), to display on 3 tableview cells.
I have the ViewController class and the tableview cell class listed below. The tableview displays the imageview pic. I assume you would place the color array in cellForRowAt method.
import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var colors:[UIImage] = [
UIImage(named: "blue")!,
UIImage(named: "red")!,
UIImage(named: "red")!
]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 118
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! customtv
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
class customtv: UITableViewCell {
lazy var backView : UIView = {
let view = UIView(frame: CGRect(x: 10, y: 6, width: self.frame.width , height: 110))
view.backgroundColor = .green
return view
}()
lazy var pic : UIImageview = {
let view = UIImageview(frame: CGRect(x: 100, y: 6, width: 100 , height: 100))
view.backgroundColor = .red
return view
}()
override func layoutSubviews() {
backView.clipsToBounds = true
backView.frame = CGRect(x: 0, y: 6, width: bounds.maxX , height: 110)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(animated, animated: true)
addSubview(backView)
addSubview(pic)
}
}
Try with this example below
import UIKit
class ViewController1: UIViewController {
var tableView: UITableView?
var colors:[UIImage] = [
UIImage(named: "blue")!,
UIImage(named: "red")!,
UIImage(named: "red")!
]
override func viewDidLoad() {
super.viewDidLoad()
loadUI()
}
func loadUI() {
tableView = UITableView()
self.view.addSubview(tableView.unsafelyUnwrapped)
tableView?.register(CustomTVC.self, forCellReuseIdentifier: "cell")
tableView?.delegate=self
tableView?.dataSource=self
tableView?.translatesAutoresizingMaskIntoConstraints = false
tableView?.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView?.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView?.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView?.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}
extension ViewController1: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 118
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTVC
cell.pic.image = colors[indexPath.row]
return cell
}
}
class CustomTVC: UITableViewCell {
lazy var backView : UIView = {
let view = UIView()
view.backgroundColor = .green
return view
}()
lazy var pic : UIImageView = {
let view = UIImageView()
view.backgroundColor = .red
return view
}()
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func layoutSubviews() {
backView.clipsToBounds = true
backView.frame = CGRect(x: 0, y: 6, width: bounds.maxX , height: 110)
}
func commonInit() {
contentView.addSubview(backView)
backView.translatesAutoresizingMaskIntoConstraints = false
backView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4).isActive = true
backView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 4).isActive = true
backView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -4).isActive = true
backView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4).isActive = true
backView.addSubview(pic)
pic.translatesAutoresizingMaskIntoConstraints = false
pic.topAnchor.constraint(equalTo: backView.topAnchor, constant: 4).isActive = true
pic.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 4).isActive = true
pic.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
pic.widthAnchor.constraint(equalTo: pic.heightAnchor).isActive = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(animated, animated: true)
}
}
I am trying to implement swipe to delete feature with two options, one is to delete and another one is to edit. The things I want is these options should be vertical rather than horizontal.
Thanks in advance for support.
You can easily achieve this swipe to reveal option feature using Custom TablViewCell
Design a view with two buttons and add a swipe gesture to the view to reveal the vertically aligned buttons
Anyway, I think you would rather use default method editActionsForRowAt for similar cases. If not I hope this code will help you.
class TableViewController: UIViewController {
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier)
return tableView
}()
var selectedCell: CustomCell?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupConstraints()
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
extension TableViewController: UITableViewDelegate & UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.identifier, for: indexPath) as? CustomCell else {
return UITableViewCell()
}
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
44
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
selectedCell?.setInitialState()
}
}
extension TableViewController: CustomCellDelegate {
func didUpdateState(customCell: CustomCell?) {
if customCell != selectedCell {
selectedCell?.setInitialState()
}
selectedCell = customCell
}
}
protocol CustomCellDelegate: class {
func didUpdateState(customCell: CustomCell?)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
static let identifier = "Cell"
private let customViewWidth: CGFloat = 100
private let customView: CustomView = {
let view = CustomView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
private let fakeContentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
enum CustomCellState {
case hiddenCustomView
case showedCustomView
}
private var trailingConstraint = NSLayoutConstraint()
private var cellState: CustomCellState = .hiddenCustomView
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .yellow
let panGesture = UIPanGestureRecognizer(target: self, action:(#selector(handleGesture(_:))))
fakeContentView.addGestureRecognizer(panGesture)
contentView.addSubview(fakeContentView)
fakeContentView.addSubview(customView)
setConstraints()
updateCellState()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setInitialState() {
self.trailingConstraint.constant = 0
UIView.animate(withDuration: 0.5, animations: {
self.contentView.layoutIfNeeded()
})
}
#objc private func handleGesture(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: fakeContentView)
let dx = frame.width - location.x
updateFrame(deltaX: dx)
if recognizer.state == .ended {
cellState = dx > customViewWidth / 2 ? .showedCustomView : .hiddenCustomView
updateCellState()
delegate?.didUpdateState(customCell: self)
}
}
private func updateFrame(deltaX: CGFloat) {
guard abs(deltaX) <= customViewWidth else {
return
}
trailingConstraint.constant = -deltaX
contentView.layoutIfNeeded()
}
private func updateCellState() {
let dx: CGFloat = cellState == .hiddenCustomView ? 0 : customViewWidth
self.trailingConstraint.constant = -dx
UIView.animate(withDuration: 0.5, animations: {
self.contentView.layoutIfNeeded()
}, completion: nil)
}
private func setConstraints() {
trailingConstraint = fakeContentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
trailingConstraint.isActive = true
NSLayoutConstraint.activate([
fakeContentView.topAnchor.constraint(equalTo: contentView.topAnchor),
fakeContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
fakeContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
customView.topAnchor.constraint(equalTo: fakeContentView.topAnchor),
customView.bottomAnchor.constraint(equalTo: fakeContentView.bottomAnchor),
customView.leadingAnchor.constraint(equalTo: fakeContentView.trailingAnchor),
customView.widthAnchor.constraint(equalToConstant: customViewWidth)
])
}
}
class CustomView: UIView {
private let button1: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .blue
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let button2: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .black
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .gray
addSubview(button1)
addSubview(button2)
setConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setConstraints() {
NSLayoutConstraint.activate([
button1.topAnchor.constraint(equalTo: topAnchor),
button1.leadingAnchor.constraint(equalTo: leadingAnchor),
button1.trailingAnchor.constraint(equalTo: trailingAnchor),
button1.bottomAnchor.constraint(equalTo: centerYAnchor),
button2.bottomAnchor.constraint(equalTo: bottomAnchor),
button2.leadingAnchor.constraint(equalTo: leadingAnchor),
button2.trailingAnchor.constraint(equalTo: trailingAnchor),
button2.topAnchor.constraint(equalTo: button1.bottomAnchor)
])
}
}