Make UITableViewCell actions overlap content - ios

I was trying to go about making the actions for a UITableViewCell overlap the content of the cell, like in the National Geographic app:
table view
table view when swiped
I tried using a listener on the contentView of the cell to track the frame and keep it constant, but I was unable to get that to work (although it's possible it would, I'm kinda new to iOS).
If anyone has any suggestions for creating a similar effect, they would be much appreciated!

u can make your own custom cell and add a delete button and swipe gestures to make like button overlap the contents of the cell for example, try it out yourself, first create a sample project with single view app, and proceed
subclass the tabview cell with xib option selected and name it something like CustomCell , and in CustomCell.swift class past below code
import UIKit
class CustomCell: UITableViewCell {
var deleteButton:UIButton!
//create a custom cell if not in the reuse pool
class func customCell() -> CustomCell?
{
let aVar:Array = NSBundle.mainBundle().loadNibNamed("CustomCell", owner: nil, options: nil)
if aVar.last!.isKindOfClass(UITableViewCell)
{
return aVar.last as? CustomCell
}
return nil
}
//handle your left swipe
func swipeLeft()
{
print("swipe Left")
self.contentView.bringSubviewToFront(deleteButton!)
var frameRect:CGRect! = deleteButton!.frame
frameRect.origin.x = self.contentView.bounds.size.width - self.deleteButton!.frame.size.width
UIView.animateWithDuration(0.5) { () -> Void in
self.deleteButton!.frame = frameRect
}
}
//aslo the right swipe
func swipeRight()
{
print("swipe right")
var rect:CGRect! = deleteButton?.frame
rect.origin.x = self.contentView.bounds.size.width
UIView.animateWithDuration(0.5) { () -> Void in
self.deleteButton!.frame = rect
}
}
//hear we are adding the delete button in code, if u want add it in xib or (if u are using storyboard )
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
deleteButton = UIButton.init(type: .Custom)
deleteButton.setTitle("Delete", forState: .Normal)
deleteButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
deleteButton.backgroundColor = UIColor.redColor()
self.contentView.addSubview(deleteButton)
let gestureLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer.init(target: self, action: "swipeLeft")
gestureLeft.direction = .Left
self .addGestureRecognizer(gestureLeft)
let gestureRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer.init(target: self, action: "swipeRight")
gestureRight.direction = .Right
self .addGestureRecognizer(gestureRight)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//set the initial frame of delete button
override func layoutSubviews() {
super.layoutSubviews()
var rect:CGRect! = deleteButton?.frame
rect.size = CGSizeMake(100, 100)
rect.origin.x = self.contentView.bounds.size.width
deleteButton?.frame = rect
}
}
and make sure the tableview cell height to be of 100pt and in view controller set up a tableview in storyboard with datasource and delegate and implement the required delegate and datasource methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? CustomCell
if cell == nil
{
cell = CustomCell .customCell()
}
return cell as CustomCell!
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}

Related

Animate selected row on button tap

I currently have a table with a custom cell that contains a button and a label. Everything is working fine, the label displays the product name and the button responds to a button tap.
What I would like to be able to do is animate the width of the row/cell where the button was tapped.
Here is the code I currently have...
Main ViewController:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = items[indexPath.row]
cell.displayProductName.text = data.productName
cell.buttonAction = { sender in
print("Button tapped...")
}
return cell
}
}
Custom Cell
class CustomCell: UITableViewCell {
var buttonAction: ((UIButton) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func activeInactive(_ sender: UIButton) {
self.buttonAction?(sender)
}
}
Table View
How can I animate the width of the row where the button was tapped?
You could subclass UIButton and add an index property where you can store the represented index for the button:
import UIKit
class UIButtonWithIndex: UIButton {
var representedIndex: Int?
}
Then you should use this class in the storyboard instead UIButton.
After that add the button as an outlet in your cell and connect in in Interface Builder.
class CustomCell: UITableViewCell {
#IBOutlet weak var button: UIButtonWithIndex!
var buttonAction: ((UIButtonWithIndex) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func activeInactive(_ sender: UIButtonWithIndex) {
self.buttonAction?(sender)
}
// Reset the represented index when reusing the cell
override func prepareForReuse() {
//Reset content view width
button.representedIndex = nil
}
}
Then when you dequeue the cell in cellForRowAt indexPath set the representedIndex property. Now in your buttonAction you should have the index of the row in which the button was tapped.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = items[indexPath.row]
cell.displayProductName.text = data.productName
cell.button.representedIndex = indexPath.item
cell.buttonAction = { sender in
print("Button tapped... \(sender.representedIndex)")
}
return cell
}
Once you have the index you can retrieve the cell with cellForRowAtIndexPath
I'd recommend to:
Create subview, let's call it "resizeableContentView"
Add "resizeableContentView" as a child to cell's "contentView"
Add your views to "resizeableContentView" as a child
Set .clearColor for cell and "contentView" background color if needed
Set width for "resizeableContentView" by action
Don't forget reset cell when it's reused
you can try this boilerplate code:
cell.buttonAction = { sender in
UIView.animate(withDuration: 0.5) {
cell.frame = CGRect(x: 0, y: 0, width: 150, height: 50)
}
}

Adding a gesture recognizer to an image view in a table cell

How can I add a Gesture Recognizer to a UIImageView in a table cell? I want it so that if a user taps an image in the cell, the image will change and the data model will update.
I know this needs to be set up in the UITableViewController. My code currently can execute a command if anywhere in the cell is tapped, but I would like it to execute only if the image is tapped, not anywhere in the cell.
I setup up the gesture recognizer in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Load sample data
loadSampleHabits()
// Initialize tap gesture recognizer
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to the view
self.tableView.addGestureRecognizer(recognizer)
And this is the function
//action method for gesture recognizer
func tapEdit(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? HabitTableViewCell {
print("Row Selected")
}
}
}
As a secondary question, are there any conflicts if I want to add a gesture recognizer to the cell and the image view within the cell?
You are adding gesture recognizer on your tableview instead of imageView as you required. Yo need to move your code from viewDidLoad to cellForRowAtIndexPath and add gesture to imageView in each cell while configuing your cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to your image view
cell.yourimageview.addGestureRecognizer(recognizer)
}
Note: Do make sure to enable userinteraction of your image view
cell.yourimageview.userInteractionEnabled = YES;
For your requirement I will suggest using UILongPressGestureRecognizer as it has less chances of conflict in gesture and didselect. Yo can add UILongPressGestureRecognizer in viewDidLoad and access it as per your requirement.
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleLongPress(_:)))
lpgr.minimumPressDuration = 1
tableView.addGestureRecognizer(lpgr)
Define method as
func handleLongPress(_ gesture: UILongPressGestureRecognizer){
if gesture.state != .began { return }
let tapLocation = gesture.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? HabitTableViewCell {
print("Row Selected")
}
}
You can try removing if recognizer.state == UIGestureRecognizerState.ended condition from your method.
UITapGestureRecognizer is a discrete gesture, and as such, your event handler is called only once when the gesture was recognized. You don't have to check the state at all. Certainly you won't receive a call for the state of .Began. For more info consider #Rob ans here.
Add This line in cell for row at index path
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to the view
cell.yourimageviewname.addGestureRecognizer(recognizer)
cell.yourimageviewname.userInteractionEnabled = true;
For my suggestion you have to use UIButton in cell, for performance
improvements,
UIButtons
Specially designed for this and have been extensively optimized by Apple for touches.
If you want image in cell you can use UIButton with Image inside.
I have had design a solution like this. I just write a sample code below:
import UIKit
protocol CellImageTapDelegate {
func tableCell(didClickedImageOf tableCell: UITableViewCell)
}
class SampleCell : UITableViewCell {
var delegate : CellImageTapDelegate?
var tapGestureRecognizer = UITapGestureRecognizer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
tapGestureRecognizer.addTarget(self, action: #selector(SampleCell.imageTapped(gestureRecgonizer:)))
self.addGestureRecognizer(tapGestureRecognizer)
}
func imageTapped(gestureRecgonizer: UITapGestureRecognizer) {
delegate?.tableCell(didClickedImageOf: self)
}
}
class ViewController: UITableViewController, CellImageTapDelegate {
// CellImageTapDelegate
func tableCell(didClickedImageOf tableCell: UITableViewCell) {
if let rowIndexPath = tableView.indexPath(for: tableCell) {
print("Row Selected of indexPath: \(rowIndexPath)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCellID", for: indexPath) as! SampleCell
cell.delegate = self
return cell
}
}
remember to do following in storyboard
1. enable user interaction of imageview
2. set class of tableviewcell
3. set reuse identifier of tableviewcell
// create an instance of UITapGestureRecognizer and tell it to run
// an action we'll call "handleTap:"
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
// we use our delegate
tap.delegate = self
// allow for user interaction
cell.imageViewName.userInteractionEnabled = true
// add tap as a gestureRecognizer to tapView
cell.imageViewName.addGestureRecognizer(tap)
import UIKit
class UserInfoCell: UITableViewCell{
#IBOutlet weak var imagePlaceholder: UIImageView!
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
let imagePicker = UIImagePickerController()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserInfoCell" ,for: indexPath ) as! UserInfoCell
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.openGallery))
cell.imagePlaceholder.addGestureRecognizer(recognizer)
recognizer.numberOfTapsRequired = 1
cell.imagePlaceholder.isUserInteractionEnabled = true
cell.name.text = "Akshay"
if let data = UserDefaults.standard.data(forKey: "savedImage") {
cell.imagePlaceholder.image = UIImage(data: data as Data)
}
return cell
}
#objc func openGallery(){
imagePicker.sourceType = .photoLibrary
present(imagePicker,animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let userimage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let imageData = userimage.jpegData(compressionQuality: 1)!
UserDefaults.standard.setValue(imageData, forKey: "savedImage")
print("image found")
self.imagePicker.dismiss(animated: true, completion: nil)
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
imagePicker.delegate = self
tableView.tableFooterView = UIView()
}
}
This code select image from gallery using Tapgesture of ImageView inside a TableViewCell

How to add XYPieChart and PNBarChart in same prototype cell

My requirement is simple. I want to load a pie chart in first index of a UITableView. When I swipe right, pie chart (XYPieChart) should be replaced with a bar chart (PNBarChart). I have separately loaded pie chart and bar chart in separate rows. How can I achieve that swipe effect in UITableview.?
A UIView has to be added into the prototype cell and custom class of UIView has to set to XYPieChart and PNBarChart in the Identity Inspector in Xcode.
What I expect is an effect like page controller, but page controller loads two view controllers. Right?
Can I add a Page Control inside a prototype cell and add two UIViews into the page control?
Will the views change when I swipe the row?
I'm attaching some screenshots with this for clarity.
Pie Chart Screenshot
Pie Chart and Bar Chart in two rows
Try like this But code is in Swift
First Create Tableviewcontroller from Storyboard
Create CustomCell of TableView
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControll: UIPageControl!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Now In Tableviewcontroller and add delegate UIScrollViewDelegate,UITableViewDelegate,UITableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:CustomCell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CustomCell;
let scrollViewWidth:CGFloat = tableView.frame.width
let scrollViewHeight:CGFloat = cell.contentView.frame.height
let imgOne = UIImageView(frame: CGRect(x:0, y:0,width:scrollViewWidth, height:scrollViewHeight))
imgOne.image = UIImage(named: "image1")
imgOne.tag = 1
let imgTwo = UIImageView(frame: CGRect(x:scrollViewWidth, y:0,width:scrollViewWidth, height:scrollViewHeight))
imgTwo.image = UIImage(named: "image2")
imgTwo.tag = 1
cell.scrollView.addSubview(imgOne)
cell.scrollView.addSubview(imgTwo)
cell.scrollView.contentSize = CGSize(width:scrollViewWidth * 2, height:cell.scrollView.frame.height)
cell.scrollView.delegate = self
cell.pageControll.currentPage = 0
pageCtrl = cell.pageControll
return cell;
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){
if (scrollView.superview?.superview as? CustomCell) != nil{
let cell = scrollView.superview?.superview as! CustomCell
let pageWidth:CGFloat = self.view.frame.width
let currentPage:CGFloat = floor((scrollView.contentOffset.x - pageWidth/2)/pageWidth)+1
cell.pageControll.currentPage = Int(currentPage)
}
}

Why my Tableview is reusing some cells?

I have a Todo list, i have a label and a button in the same cell, when i click the button, change the image button for that cell, but when i scrolled the table view the same button appears on the others cells, it was not to appear in cells that the button were not pressed.
Here is my cellForRowAtIndexPath code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! TaskTableViewCell
cell.label.text = "Task Number: \(indexPath.row + 1)"
cell.btnFavorite.setImage(UIImage(named: "star"), forState: .Normal)
cell.btnFavorite.setImage(UIImage(named: "star-filled"), forState: .Selected)
cell.btnFavorite.addTarget(self, action: #selector(ListOfTasksViewController.addRemoveFavoriteList), forControlEvents: .TouchUpInside)
return cell
}
func addRemoveFavoriteList(sender : UIButton) {
sender.selected = !sender.selected
}
Custom TableViewCell Class:
import UIKit
class TaskTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var btnFavorite: FavoriteButton!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
super.prepareForReuse()
label.text = nil
btnFavorite.selected = false
}
}
View Controller:
import UIKit
class ListOfTasksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! TaskTableViewCell
cell.label.text = "Task Number: \(indexPath.row + 1)"
cell.btnFavorite.indexPath = indexPath.row
cell.btnFavorite.addTarget(self, action: #selector(ListOfTasksViewController.addRemoveFavoriteList), forControlEvents: .TouchUpInside)
return cell
}
func addRemoveFavoriteList(sender : FavoriteButton) {
if sender.selected {
sender.selected = false
} else {
sender.selected = true
let index = NSIndexPath(forRow: sender.indexPath, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(index) as! TaskTableViewCell
}
}
}
The cells in your table view are reused so as you scroll down, the cells going off screen are being put at the start of the queue before going back onto the screen at a different indexPath. This can cause some issues so you need to override the prepareForReuse method in your custom cell class.
override func prepareForReuse() {
super.prepareForReuse()
label.text = nil
btnFavorite.selected = false
}
you need to add condition in the cellforaRowAtIndexPath.
you need to add flag in Array which track your selection.
then its check in cellforRowAtIndexPath.
for example
button.selected= No
if arrselectedIndexpath containObject:indexPath{
button.selected =yes
}

When I swipe a UITableViewCell, the header view moves as well

So, I have a few Swipe actions like delete, block, etc in my UITableView. I wanted to add headers to separate my two sections. So, I added a prototype cell, named it HeaderCell and then went to the view. I added one label, named headerLabe. My problem is that when I swipe for the actions, the header cells were moving as well, which looked bad. I researched, and found a solution to just return the contentView of the cell. However, when I do this, the label has not shown up. I have tried a dozen different solutions, and nothing has worked, so I have turned to SO. Can anyone help me?
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell : CustomHeaderTableViewCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderTableViewCell
if section == 0 {
headerCell.headerLabel.text = "Thank You's"
} else if section == 1 {
headerCell.headerLabel.text = "Conversations"
}
return headerCell.contentView
}
Thanks so much.
You can use a section Header as #ozgur suggest.If you still want to use a cell.
Refer to this datasource method
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath = YourHeaderCellIndexPath{
return false
}
return true
}
check the following methods
In your UIViewController use the following
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! WishListHeaderCell
headerCell.lblTitle.text = cartsData.stores_Brand_Name
let imgVw = UIImageView()
imgVw.frame = CGRectMake(8, 18, 25, 25)
imgVw.image = UIImage(named: "location.png")
let title = UILabel()
title.frame = CGRectMake(41, 10, headerCell.viwContent.frame.width - 49, 41)
title.text = cartsData.stores_Brand_Name
title.textColor = UIColor.whiteColor()
headerCell.viwContent.addSubview(imgVw)
headerCell.viwContent.addSubview(title)
return headerCell.viwContent
}
In your UITableViewCell use the following
import UIKit
class HeaderCell: UITableViewCell {
#IBOutlet weak var viwContent: UIView!
#IBOutlet weak var imgIcn: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.viwContent.backgroundColor = UIColor.grayColor()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//UITableViewCell
let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") as! SecJobCCHeaderTableViewCell
// Cell Rect
var cellRect : CGRect = headerCell.frame
cellRect.size.width = screenBounds.width
// Header Footer View
let headerFooterView = UITableViewHeaderFooterView(frame : cellRect)
//Adding Gesture
let swipeGestRight = UISwipeGestureRecognizer(target: self, action:#selector(AddSecJobCostCentreViewController.draggedViewRight(_:)))
swipeGestRight.enabled = true
swipeGestRight.direction = UISwipeGestureRecognizerDirection.Right
headerFooterView.addGestureRecognizer(swipeGestRight)
// Update Cell Rect
headerCell.frame = cellRect
// Add Cell As Subview
headerCell.tag = 1000
headerFooterView.addSubview(headerCell)
// Return Header Footer View
return headerFooterView
}
func draggedViewRight(sender:UISwipeGestureRecognizer) {
// Swipe Gesture Action
let currentHeaderView = sender.view?.viewWithTag(1000) as! SecJobCCHeaderTableViewCell
}

Resources