I have a button in a TableViewCell the overlaps the welcome TableViewCell. It looks fine when the view loads, however when I scroll down the tableview and scroll back up the button is cut off by the next tableviewcell.
I found a hack to make the background clear for the message TableViewCell, however, the bottom of the button is still not clickable.
Is there a way to set the priority of the button to be on top of the next?
HomeTableViewController.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "heroCell", for: indexPath) as! HeroTableViewCell
heroCell = cell
cell.separatorInset = UIEdgeInsets(top: 0, left: 10000, bottom: 0, right: 0)
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "welcomeCell", for: indexPath) as! WelcomeTableViewCell
// Set Intro Name
if let user = Auth.auth().currentUser {
// User is signed in
// References to firebase
let userFS = Firestore.firestore().collection("users")
// Set the navigation title to users name
userFS.document(user.uid).getDocument { (document, error) in
if let userInfo = document {
let firstName = userInfo["firstName"] as! String
cell.introLabel.text = "Hey, \(firstName)"
} else {
print("User name does not exist")
}
}
}
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: "tripsCell", for: indexPath) as! TripTableViewCell
return cell
case 3:
let cell = tableView.dequeueReusableCell(withIdentifier: "suggestionsCell", for: indexPath) as! SuggestionTableViewCell
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "popularsCell", for: indexPath) as! PopularTableViewCell
return cell
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.section {
case 0: return 425
case 1: return 200
case 2: return 400
case 3: return 400
default: return 400
}
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 425
}
HeroTableViewCell.swift
class HeroTableViewCell: UITableViewCell {
// MARK: - Outlets
#IBOutlet weak var heroImage: UIImageView!
#IBOutlet weak var heroImageViewTopConstraint: NSLayoutConstraint!
#IBOutlet weak var planTripButton: SpringButton!
// MARK: - Variables
let notification = UINotificationFeedbackGenerator()
override func awakeFromNib() {
}
// MARK: - Actions
#IBAction func addTripButtonPressed(_ sender: UIButton) {
planTripButton.animation = "pop"
planTripButton.animate()
notification.notificationOccurred(.success)
}
}
If I understand your question correctly, you want + button to hover over the UITableView, so that it is clickable regardless of which cell the user has scrolled to?
In the image below, I have place my action button (the pencil in the top right of the view) at the same level in the view hierarchy as my tableView. So as the user scrolls, that button is always visible and the user can click at any time.
Please note where the Action Button sits relative to the tableView in the Document outline. It is at the same level. It is not a subview of the tableView.
Related
I am trying to add an action to my "like" button. So that when the user taps the heart UIButton in a cell, the heart in the cell they tapped updates to a pink heart showing that they liked it. But instead it likes the heart they tapped and another random heart in a different cell that they did not interact with. I have been on this all day and any help would be grateful. For Example, if I like/tap my heart UIButton the buttons image I tapped updates, but when I scroll down another random heart updates from that same first cell button tap.
Also When I scroll and the cell leaves view and scroll back up the image returns back to unlike and other like buttons become liked.
Keep a data model for your buttons state
Try with the below code
struct TableModel {
var isLiked: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataSource: [TableModel] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
dataSource = Array(repeating: TableModel(isLiked: false), count: 20)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataSource.count
}
#objc func buttonSelected(_ sender: UIButton) {
dataSource[sender.tag].isLiked = !dataSource[sender.tag].isLiked
let indexPath = IndexPath(row: sender.tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.likeBtn.tag = indexPath.row
cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
//set unlike image
}
return cell
}
}
Currently, you have a hardcoded number of rows, but anyway you will need to have a data source with data models. When you press the button, you have to save the state of the button of a specific row. I would recommend you create a model first.
Here I provided an easy (but flexible enough) way how to do this. I haven't debugged it, but it should work and you can see the idea. I hope this would be helpful.
Create Cell Model
struct CellViewModel {
let title: String
var isLiked: Bool
// Add other properties you need for the cell, image, etc.
}
Update cell class
It's better to handle top action right in the cell class. To handle this action on the controller you can closure or delegate like I did.
// Create a delegate protocol
protocol TableViewCellDelegate: AnyObject {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
// add a delegate property
weak var delegate: TableViewCellDelegate?
#IBOutlet var titleTxt: UILabel!
#IBOutlet var likeBtn: UIButton!
//...
override func awakeFromNib() {
super.awakeFromNib()
// You can add target here or an action in the Storyboard/Xib
likeBtn.addTarget(self, action: #selector(likeButtonSelected), for: .touchUpInside)
}
/// Method to update state of the cell
func update(with model: CellViewModel) {
titleTxt.text = model.title
likeBtn.isSelected = model.isLiked
// To use `isSelected` you need to set different images for normal state and for selected state
}
#objc private func likeButtonSelected(_ sender: UIButton) {
sender.isSelected.toggle()
delegate?.didSelectLikeButton(isLiked: sender.isSelected, forCell: self)
}
}
Add an array of models and use it
This is an updated class of ViewController with usage of models.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Provide a list of all models (cells)
private var cellModels: [CellViewModel] = [
CellViewModel(title: "Title 1", isLiked: false),
CellViewModel(title: "Title 2", isLiked: true),
CellViewModel(title: "Title 3", isLiked: false)
]
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return count of cell models
return cellModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let model = cellModels[indexPath.row]
// call a single method to update the cell UI
cell.update(with: model)
// and you need to set delegate in order to handle the like button selection
cell.delegate = self
return cell
}
}
extension ViewController: TableViewCellDelegate {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell) {
// get an indexPath of the cell which call this method
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
// get the model by row
var model = cellModels[indexPath.row]
// save the updated state of the button into the cell model
model.isLiked = isLiked
// and set the model back to the array, since we use struct
cellModels[indexPath.row] = model
}
}
I have a record button in a custom cell in a UITableView with two images - when you press the button it shows the image for 'stop' record and when you stop record it shows you the image for 'record'. However, when I press the record button the stop image changes on another row in the tableview and not in the button of the cell I have just pushed.
I have tried to make the behaviour of the button consistent by using a delegate protocol method to set up the custom cell. But this has not resolved the issue.
Code for the custom cell (CreateStoryCell) is as follows:
import UIKit
protocol createStoryCellDelegate {
func didRecord(sender: UIButton, storyItem: StoryItem)
}
class CreateStoryCell: UITableViewCell {
#IBOutlet weak var storyCellBackground: UIView!
#IBOutlet weak var storyTextLabel: UILabel!
#IBOutlet weak var record: UIButton!
var storyItem : StoryItem!
var delegate: createStoryCellDelegate?
func setStoryItem(storyPart: StoryItem) {
storyItem = storyPart
storyTextLabel.text = storyItem.text
}
#IBAction func recordTapped(_ sender: UIButton) {
delegate?.didRecord(sender: sender, storyItem: storyItem)
}
}
The extension for the setting up of the cell for tableView is here:
extension CreateStoryViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "createStoryCell", for: indexPath) as! CreateStoryCell
let storyItem = storyText?[indexPath.row]
cell.setStoryItem(storyPart: storyItem!)
cell.delegate = self
return cell
}
}
And the didRecord method is here:
extension CreateStoryViewController : createStoryCellDelegate {
func didRecord(sender: UIButton, storyItem: StoryItem) {
if recordToggle == 1 {
sender.setImage(UIImage(named: "recordStop"), for: .normal)
checkRecording(storyItem: storyItem)
audioRecorder.record()
recordToggle = 2
} else {
sender.setImage(UIImage(named: "smallMicBtn"), for: .normal)
audioRecorder.stop()
recordToggle = 1
}
}
}
Please also note that the above delegate method begins to work until I insert the record functionality i.e. as soon as I make active the 'audioRecorder.record()' and 'audioRecorder.stop() lines the button behaviour becomes inconsistent as described above.
Previous, I have tried to implement this by converting the position of the record button to an indexPath with the following:
// let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to: self.createStoryTableView)
// let indexPath = self.createStoryTableView.indexPathForRow(at: buttonPosition)
// let storyItem = self.storyText?[indexPath?.row ?? 0]
But I got the same behaviour.
I'd be grateful for any suggestions on how to resolve. Thanks.
I recommend to introduce a new variable
var isRecording = false in your StoryItem and change its value on start/ stop record button click and reload that particular cell.
In your cellForRowAt use this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let storyItem = self.storyText?[indexPath?.row ?? 0]
let image = (storyItem. isRecording == true) ? (UIImage(named:"stopImage")) : (UIImage(named: "startImage"))
cell.record.setImage(image, for: .normal)
return cell
}
I have multiple sections in my tableview with multiple custom cells(cell with radio button, cell with check box and cell with textfield). Problem is under section with radio button, only one radio button should be selectable under radio button section. After selecting, I have tried scrolling, multiple radio buttons are selected. Help much appreciated.
class RedioButtonCell: UITableViewCell {
var radioButtonDelegate: RedioCellDelegate?
var cellindexPath : IndexPath?
#IBOutlet weak var btnRediouttion: UIButton?
#IBOutlet weak var lblTitle: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Cell for row method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if ((qaArray[indexPath.section]["Que"] as! [String:String])["type"]!) == CONTROL
{
let cell = tableView.dequeueReusableCell(withIdentifier: "RedioButtonCell", for: indexPath) as! RedioButtonCell
cell.btnRediouttion?.tag = Int("\(indexPath.section)" + "\(indexPath.row)")!
cell.lblTitle!.text = String(describing: ansDetailArray[indexPath.row]["survey_answer"]!)
let deselectedImage = UIImage(named: "Radio_Unselected")?.withRenderingMode(.alwaysTemplate)
let selectedImage = UIImage(named: "radio_Selected")?.withRenderingMode(.alwaysTemplate)
btnRediouttion?.setImage(deselectedImage, for: .normal)
btnRediouttion?.setImage(selectedImage, for: .selected)
btnRediouttion?.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
cell.cellindexPath = indexPath;
return cell
}
}
func radioButtonTapped(_ radioButton: UIButton) {
print("radio button tapped")
let isSelected = !(radioButton?.isSelected)!
radioButton?.isSelected = isSelected
if isSelected {
}
}
The tableView cells are reused ( happens when scrolling ) , so you need to keep track of the selected one by saving it's IndexPath and assign it inside cellForRowAt
//
declare this in your VC
var currentIndex:IndexPath?
//
class RadioButton:UIButton {
var indexPath:IndexPath
}
//
func radioButtonTapped(_ radioButton: RadioButton) {
self.currentIndex = radioButton.indexPath
}
//
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath == currentIndex {
// this should be selected
}
else {
// Deselect this
}
cell.radioButton.indexPath = indexPath
}
i am new to Swift and I have seen tons of guides on the topic. However in my case, it's working. I have a custom cell file:
class FileCell: UITableViewCell {
#IBOutlet weak var firstName: UILabel!
#IBOutlet weak var cellImage: UIImageView!
func updateImage(name: String) {
cellImage.image = UIImage(named: name)
}}
In the view controller I am using "willDisplay" function as follows:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell") as! FileCell
let user = users[indexPath.row]
if user.email.isEmpty {
//cell.cellImage.image = UIImage(named: "folder.png")
cell.updateImage(name: "folder.png")
} else {
//cell.cellImage.image = UIImage(named: "file.png")
cell.updateImage(name: "file.png")
}
}
where i try to change imageView in cell depending on the data incoming to cell. However, the images either don't display at all or cells are not showing up.
Thanks in advance for tips on what i am doing wrong.
You should not dequeue a new cell, since you already have one.
Just skip the first statement:
// let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell") as! FileCell
But one question: Why are you using the willDisplay delegate method? I would suggest to set up the cell in tableView(_:cellForRowAt:):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell") as! FileCell
let user = users[indexPath.row]
cell.firstName.text = user.name // or something
if user.email.isEmpty {
cell.updateImage(name: "folder.png")
} else {
cell.updateImage(name: "file.png")
}
return cell
}
In tableview have different section.
Want to add the radio button for all the section.
Each section have individual select and deselect in tableview.
In first section choice1,[show in fig]
Selected cheese means cheese want to select, next if user click bacon means cheese automatically deselect.
[Here using radio button SSRadioButton class for click action. Create a radio button in tableview cell. how to write the button action for radio button. or suggest any new way].
Each radio button want individual select and deselect. The same process for all the section in tableview. how is possible help me. Thanks advance.
my code:
var radioControllerChoice : SSRadioButtonsController = SSRadioButtonsController()
var radioControllerDip : SSRadioButtonsController = SSRadioButtonsController()
func numberOfSections(in tableView: UITableView) -> Int {
return table_data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return table_data[section].menu_id.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:CustomiseTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Customise") as! CustomiseTableViewCell
cell.name.text?=table_data[indexPath.section].menu_name[indexPath.row]
print(table_data[indexPath.section].customize[indexPath.row])
switch indexPath.section {
case 2:
radioControllerChoice.addButton(cell.radioBtn)
radioControllerChoice.shouldLetDeSelect = false
case 3:
radioControllerDip.addButton(cell.radioBtn)
radioControllerDip.shouldLetDeSelect = false
switch Int(table_data[indexPath.section].customize[indexPath.row]) {
case 1:
cell.radioBtn.isHidden = false
default:
print("Invalid choose")
cell.radioBtn.addTarget(self, action: #selector(ViewController.didSelectButton), for: .touchUpInside)
cell.radioBtn.tag = indexPath.row
}
}
}
func didSelectButton(selectedButton: UIButton?)
{
/// need solution for button action help me..
}
You can use UIButton instead of SSRadioButton, and then you can change the image of button for checked and unchecked radio button.
Swift3.2:
CustomiseTableViewCell
import UIKit
protocol CustomTableViewCellDelegate {
func didToggleRadioButton(_ indexPath: IndexPath)
}
class CustomiseTableViewCell: UITableViewCell {
#IBOutlet weak var itemLabel: UILabel!
#IBOutlet weak var radioButton: UIButton!
var delegate: CustomTableViewCellDelegate?
func initCellItem() {
let deselectedImage = UIImage(named: "ic_radio_button_unchecked_white")?.withRenderingMode(.alwaysTemplate)
let selectedImage = UIImage(named: "ic_radio_button_checked_white")?.withRenderingMode(.alwaysTemplate)
radioButton.setImage(deselectedImage, for: .normal)
radioButton.setImage(selectedImage, for: .selected)
radioButton.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
}
func radioButtonTapped(_ radioButton: UIButton) {
print("radio button tapped")
let isSelected = !self.radioButton.isSelected
self.radioButton.isSelected = isSelected
if isSelected {
deselectOtherButton()
}
let tableView = self.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
delegate?.didToggleRadioButton(tappedCellIndexPath)
}
func deselectOtherButton() {
let tableView = self.superview?.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
let indexPaths = tableView.indexPathsForVisibleRows
for indexPath in indexPaths! {
if indexPath.row != tappedCellIndexPath.row && indexPath.section == tappedCellIndexPath.section {
let cell = tableView.cellForRow(at: IndexPath(row: indexPath.row, section: indexPath.section)) as! CustomiseTableViewCell
cell.radioButton.isSelected = false
}
}
}
}
Call initCellItem method from UITableViewDataSource's delegate method:
// Your ViewController
let menuList = [ ["Cheese", "Bacon", "Egg"],
["Fanta", "Lift", "Coke"] ] // Inside your ViewController
var selectedElement = [Int : String]()
func didToggleRadioButton(_ indexPath: IndexPath) {
let section = indexPath.section
let data = menuList[section][indexPath.row]
if let previousItem = selectedElement[section] {
if previousItem == data {
selectedElement.removeValue(forKey: section)
return
}
}
selectedElement.updateValue(data, forKey: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:CustomiseTableViewCell =
tableView.dequeueReusableCell(withIdentifier: "Customise") as! CustomiseTableViewCell
let item = menuList[indexPath.section][indexPath.row]
cell.itemLabel.text = item
if item == selectedElement[indexPath.section] {
cell.radioButton.isSelected = true
} else {
cell.radioButton.isSelected = false
}
cell.initCellItem()
cell.delegate = self
// Your logic....
return cell
}
Alternate way:
You can use simple UIButton instead of any third party library (SSRadioButton) and use it like:
Set the UIButton's image in default state to - circle (as in the screenshot)
Set the UIButton's image in selected state to - filled circle
UIButton's action event can be captured in a normal way like you do in any other case.
Something like this:
Let me know if you want to follow this approach or need any kind of help regarding this.