in my app i have a tableview with a profile image and a username label. If you click on one of the 2 then the need to do this function:
func goToProfileScreen(gesture: UITapGestureRecognizer) {
self.performSegueWithIdentifier("profile", sender: nil)
}
however if i try to implement this in my cellForRowAtIndexPath it only works for the last time i've added it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell") as? NewsCell {
let post = self.posts[indexPath.row]
cell.request?.cancel()
let profileTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
profileTapRecognizer.numberOfTapsRequired = 1
// profileTapRecognizer.delegate = self
cell.profileImg.tag = indexPath.row
cell.profileImg.userInteractionEnabled = true
cell.profileImg.addGestureRecognizer(profileTapRecognizer)
cell.usernameLabel.tag = indexPath.row
cell.usernameLabel.userInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(profileTapRecognizer)
var img: UIImage?
if let url = post.profileImageURL {
if url != "" {
img = NewsVC.imageCache.objectForKey(url) as? UIImage
}
}
cell.configureCell(post, img: img)
cell.selectionStyle = .None
return cell
} else {
return NewsCell()
}
}
so now it works for the username label. if i put the usernamelabel first in the code and then the profileImg, then it only works for the profileImg?
how can i get it to work for both of them?
You need to use 2 different tapRecognizers, because a UITapGestureRecognizer can only be attached to one view.
("They are objects that you attach to a view", Apple Doku)
let profileTapRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
let profileTapRecognizer2 = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
profileTapRecognizer1.numberOfTapsRequired = 1
profileTapRecognizer2.numberOfTapsRequired = 1
// profileTapRecognizer1.delegate = self
// profileTapRecognizer2.delegate = self
cell.profileImg.tag = indexPath.row
cell.profileImg.userInteractionEnabled = true
cell.profileImg.addGestureRecognizer(profileTapRecognizer1)
cell.usernameLabel.tag = indexPath.row
cell.usernameLabel.userInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(profileTapRecognizer2)
Related
I have a button in my cell that if the user holds for a certain length of time it will trigger a popup. I am having trouble passing the cell data with the long press button.
Heres how I submit and pass data with a regular tap...
cell.addButton.tag = (indexPath as NSIndexPath).row
cell.addButton.addTarget(self, action: #selector(Dumps.addAction(_:)), for: UIControl.Event.touchUpInside)
.
#IBAction func addAction(_ sender: Any) {
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
codeData = cell.codeField.text! }
The above works fine.
Heres how I submit the button with the long press gesture. Its passing nil through _sender I think
cell.deleteButton.tag = (indexPath as NSIndexPath).row
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(Dumps.deleteAction(_:)))
cell.deleteButton.addGestureRecognizer(longGesture)
.
#objc func deleteAction(_ sender: UIGestureRecognizer){
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
cell.codeLabel.backgroundColor = UIColor.red }
How would I pass the data through this method?
You should be using the tag of the UIButton instead of the UILongPressGestureRecognizer as you have done above.
func deleteAction(_ sender: UILongPressGestureRecognizer) {
guard let tag = (sender.view as? UIButton)?.tag else { return }
let cell = tableView.cellForRow(at: IndexPath(row: tag, section: 0)) as? DumpsCell01
cell?.codeLabel.backgroundColor = .red
}
Note: I've also avoided force unwrapping as you should too through-out the project.
In my Table View Controller I'm requesting data from my backend. Then adding the data to an array with a view model for each row from the request.
let getNotifications = GETNotificationsByUserID(user_id: user_id)
getNotifications.getNotifications { notifications in
self.notifications = notifications.map { notification in
let ret = NotificationViewModel()
ret.mainNotification = notification
return ret
}
class NotificationViewModel {
var mainNotification: Notifications? {}
}
struct Notifications: Codable {
var supporter_picture:String?
}
In the same table view controller, I'm then adding each item from the array to a variable in my table view cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath) as! NotificationCell
cell.user_image.isUserInteractionEnabled = true
let item = self.notifications[indexPath.item]
cell.viewModel = item
return cell
}
Downloading the image from the data from my table view cell variable and setting my UIImageView image.
UITableViewCell
class NotificationCell: UITableViewCell {
var viewModel: NotificationViewModel? {
didSet {
if let item = viewModel {
self.username.text = item.mainNotification?.supporter_username
item.supporterImageDownloader = DownloadImage()
item.supporterImageDownloader?.imageDidSet = { [weak self] image in
self?.user_image.image = image
}
if let picture = item.mainNotification?.supporter_picture {
item.supporterImageDownloader?.downloadImage(urlString: picture)
} else {
self.user_image.image = UIImage(named: "profile-placeholder-user")
}
}
}
}
}
The UIImageView is created from a lazy closure variable. The lazy closure variable also holds a UITapGestureRecognizer.
UITableViewCell
lazy var user_image: UIImageView = {
let image = UIImageView()
let gesture = UITapGestureRecognizer()
gesture.addTarget(self, action: #selector(userImageClicked(_:)))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
image.addGestureRecognizer(gesture)
image.isUserInteractionEnabled = true
return image
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.isUserInteractionEnabled = true
addSubview(user_image)
user_imageContraints()
}
func user_imageContraints() {
user_image.translatesAutoresizingMaskIntoConstraints = false
user_image.topAnchor.constraint(equalTo:topAnchor, constant: 8).isActive = true
user_image.leadingAnchor.constraint(equalTo:leadingAnchor, constant: 8).isActive = true
user_image.heightAnchor.constraint(equalToConstant: 40).isActive = true
user_image.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
#objc func userImageClicked(_ sender: UITapGestureRecognizer) {
print("image clicked")
}
For some reason when my UIImageView is clicked it doesn't do anything.
The problem is this line:
addSubview(user_image)
Change it to:
contentView.addSubview(user_image)
You must never add any views directly to a cell — only to its contentView.
I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}
OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.
I am trying to reload data in my tableview after getting the data from a delegate method. But the issue is that all the data is not coming through to the tableView function cellForRowAtIndexPath. I have set the variable self.restNames to hold the values from the delegate method which comes around 4 values but not all of them show up in the function cellForRowAtIndexPath. When I change the tabs and go back to the tableView, some data does come through but not all of it.Will appreciate any help on this. Apologies if I have missed something, I am new to Swift and haven't raised much questions in StackOverflow.
Regards,
Saurabh
Below is my the code for the tableViewController
class RestTableViewController: UITableViewController,getDistanceTime {
var RestTable = [Restaurant]()
let label = UILabel()
let menuUrl = "menu url"
let listProjectUrl = "url"
override func viewDidLoad() {
super.viewDidLoad()
let headerView = self.tableView
headerView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
headerView.tableHeaderView?.backgroundColor = UIColor.redColor()
self.tableView.tableHeaderView = self.tableView.tableHeaderView
self.tableView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
let token = keychain[""]
let menuData = Alamofire.request(Method.GET, self.menuUrl, headers: ["Authorization":"JWT \(token!)"])
menuData.responseJSON{ response in
let data = JSON(response.result.value!)
var localmenu = [menu]()
for (_,item) in data{
let menuOne = menu(place: item["place"].string!, types: item["types"].string!, name: item["name"].string!, price: item["price"].string!)
localmenu.append(menuOne)
}
self.menus = localmenu
}
}
// Delegate method
func loadWithDisTime(distance: String,name: String)
{
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.userDistanceFromLocation = distance
//This will hold 4 values
self.restNames = name
self.tableView.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.getDistance = getDistanceTimeVC()
self.getDistance.delegate = self
let restCount = self.RestTable.count
if restCount == 0
{
let alertView = UIAlertController(title: "There are no restaurants near your location", message: "Press Okay to go back", preferredStyle: UIAlertControllerStyle.Alert)
let alertAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(alertAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
for item in 0...restCount - 1
{
self.restLat = self.RestTable[item].lat
self.restLng = self.RestTable[item].lng
self.getDistance.getDistance(self.userLat, userLng: self.userLng, restLat: self.restLat, restLng: self.restLng,restName: self.RestTable[item].name)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.RestTable.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! RestTableViewCell
//Not all values coming through here
print(self.restNames)
if self.RestTable[indexPath.row].name == self.restNames
{
cell.textLabel?.text = "\(indexPath.row + 1) - \(self.RestTable[indexPath.row].name) - \(self.userDistanceFromLocation)"
cell.backgroundColor = UIColor.whiteColor()
cell.textLabel?.textColor = UIColor.blackColor()
cell.textLabel?.font = UIFont(name: "Avenir-Heavy", size: 15)
}
return cell
}
In this block leave only reload table view
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.tableView.reloadData()
}
I'm trying to create a checkbox functionality in a tableview .
It's a to-do list where if the checkbox is pressed the cell hides and appears as completed in a different section.
Coming from C# this was very straightforward to do but in swift there isn't even a checkbox button to start with :(...
I made it to work by adding a button with two images(checked, unchecked) to a custom prototype cell in IB.
Since you can't have the tableView and the in-cell-button declared in the same viewcontroller/class I had to subclass the tableViewCell.
Now, how do I access the checkbox from didSelectRowAtIndexPath ? When I select the cell the event fires but when I press the checkbox button in the same cell nothing fires and I can't hide the cell.
var indexTag = checkBoxImage.tag
//this is what I have in TableViewCell class
#IBAction func checkBoxInCell(sender: UIButton) {
checkBoxImage.setImage(UIImage(named:"checked"),forState:UIControlState.Normal)
if isChecked != false {
isChecked = false
cellitemcontent.removeAtIndex(indexTag)
//can't access the cell from here to update the tableview
}
else {
isChecked = true
checkBoxImage.setImage(UIImage(named:"unchecked"),forState:UIControlState.Normal)
}
}
//this is what I have in my FirstViewController that contains the tableview
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
cellitemcontent.removeAtIndex(indexTag)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
//when I press the checkBoxImage button in the cell it doesn't fire this event...
}
I use sample code below to implement check box button in cell (Xcode 7/ Swift 2.0):
-In viewDidLoad {}:(save check box state in each cell to .plist file)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
//if (let a != (fM.fileExistsAtPath(fnstring))) {
if !(fM.fileExistsAtPath(fnstring)) {
let onlyarr = NSMutableArray()
for var i = 0 ; i < 5; ++i{ // number of sections
var arr = NSArray()
switch i { // number cell in each section
case 0: arr = [Int](count: 12, repeatedValue: 0) // 0 means unchecked box.
case 1: arr = [Int](count: 13, repeatedValue: 0)
case 2: arr = [Int](count: 14, repeatedValue: 0)
case 3: arr = [Int](count: 15, repeatedValue: 0)
case 4: arr = [Int](count: 16, repeatedValue: 0)
default: arr = [Int]()
}
onlyarr.addObject(arr)
}
onlyarr.writeToFile(fnstring, atomically: false)
}
- In override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { }
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
if fM.fileExistsAtPath(fnstring) {
let chcklstDick = NSMutableArray(contentsOfURL: NSURL(fileURLWithPath: fnstring))
let chcklstsortedCat: NSArray? = chcklstDick
let ttt: NSMutableArray = chcklstsortedCat?.objectAtIndex(indexPath.section) as! NSMutableArray
if ttt.count > 0 {
var img = UIImage()
let j : Int = ttt.objectAtIndex(indexPath.row) as! Int
NSLog("j: %i", j)
if j > 0 {
img = UIImage(named: "checked")!
}else {
img = UIImage(named: "unchecked")!
}
let bttn : UIButton = UIButton(type: UIButtonType.Custom)
bttn.frame = CGRectMake(0, 0, img.size.width, img.size.height)
bttn.setBackgroundImage(img, forState: UIControlState.Normal)
bttn.addTarget(self, action:"chckBttnTapped:eventy:", forControlEvents: UIControlEvents.TouchUpInside)
cell.accessoryView = bttn
}
}else {NSLog("nonononononononno")}
- In func chckBttnTapped(sender: AnyObject, eventy event: AnyObject) { }
let touches: NSSet = event.allTouches()!
let touch: UITouch = touches.anyObject()! as! UITouch
let crrntTouchPos : CGPoint = touch.locationInView(self.tableView)
let idxpth: NSIndexPath = self.tableView.indexPathForRowAtPoint(crrntTouchPos)!
if idxpth.row != NSNotFound {
self.tableView(self.tableView, accessoryButtonTappedForRowWithIndexPath: idxpth)
}
-In override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) { }
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let fnstring = (documentsPath as String)+"/yr-filename.plist"
let fM = NSFileManager.defaultManager()
if fM.fileExistsAtPath(fnstring) {
let chcklstDick = NSMutableArray(contentsOfURL: NSURL(fileURLWithPath: fnstring))
let chcklstsortedCat: NSMutableArray? = chcklstDick
let ttt: NSMutableArray = chcklstsortedCat?.objectAtIndex(indexPath.section) as! NSMutableArray
if ttt.count > 0 {
let j : Int = ttt.objectAtIndex(indexPath.row) as! Int
var newimg = UIImage()
if j == 0 {
newimg = UIImage(named: "checked")!
}else {
newimg = UIImage(named: "unchecked")!
}
let cell = tableView.dequeueReusableCellWithIdentifier("cellID", forIndexPath: indexPath)
let bttn : UIButton = UIButton(type: UIButtonType.Custom)
bttn.frame = CGRectMake(0, 0, newimg.size.width, newimg.size.height)
bttn.setBackgroundImage(newimg, forState: UIControlState.Normal)
bttn.addTarget(self, action:"chckBttnTapped:eventy:", forControlEvents: UIControlEvents.TouchUpInside)
cell.accessoryView = bttn
self.tableView.reloadData()
self.tableView.reloadInputViews()
ttt.replaceObjectAtIndex(indexPath.row, withObject: 1 - j)
chcklstsortedCat?.replaceObjectAtIndex(indexPath.section, withObject: ttt)
chcklstsortedCat?.writeToFile(fnstring, atomically: false)
}
}
Hope its useful.
I know what you are trying to accomplish, but I would tackle it a different way. Perhaps in your model, you could have an array of tasks that are pending, and another array of tasks that are completed.
The number of sections can be 2. The number of rows in sections 0 and 1 can be the number of elements in the first and second arrays respectively.
When didSelectRowAtIndexPath is called, you can remove the item at that index in the first array and add the task to the second array. Then you must call tableView.reloadData().
I know you want to just pick up the row and change the placement of it, but in iOS the cells get reused. It's best to update the data source and then reload the table.
For the checkmarks, you can ensure that the items in Section 0 do not have the checkmark accessory, while the items in Section 1 do. You would set the accessory in cellForRowAtIndexPath after the cell has been dequeued.