Successes so far: I have a remote data source. Data gets pulled dynamically into a View Controller. The data is used to name a .title and .subtitle on each of the reusable custom cells. Also, each custom cell has a UISwitch, which I have been able to get functional for sending out both a “subscribe” signal for push notifications (for a given group identified by the cell’s title/subtitle) and an “unsubscribe” signal as well.
My one remaining issue: Whenever the user "revisits" the settings VC, while my code is "resetting" the UISwitches, it causes the following warnings in Xcode 9.2:
UISwitch.on must be used from main thread
UISwitch.setOn(_:animated:) must be used from main thread only
-[UISwitch setOn:animated:notifyingVisualElement:] must be used from main thread
The code below "works" -- however the desired result happens rather slowly (the UISwitches that are indeed supposed to be "on" take a good while to finally flip to "on").
More details:
What is needed: Whenever the VC is either shown or "re-shown," I need to "reset" the custom cell’s UISwitch to "on" if the user is subscribed to the given group, and to "off" if the user is not subscribed. Ideally, each time the VC is displayed, something should reach out and touch the OneSignal server and find out that user’s “subscribe state” for each group, using the OneSignal.getTags() function. I have that part working. This code is in the VC. But I need to do it the right way, to suit proper protocols regarding threading.
VC file, “ViewController_13_Settings.swift” holds a Table View with the reusable custom cell.
Table View file is named "CustomTableViewCell.swift"
The custom cell is called "customCell" (I know, my names are all really creative).
The custom cell (designed in XIB) has only three items inside it:
Title – A displayed “friendly name” of a “group” to be subscribed to or unsubscribed from. Set from the remote data source
Subtitle – A hidden “database name” of the aforementioned group. Hidden from the user. Set from the remote data source.
UISwitch - named "switchMinistryGroupList"
How do I properly set the UISwitch programmatically?
Here is the code in ViewController_13_Settings.swift that seems pertinent:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
// set cell's title and subtitle
cell.textLabelMinistryGroupList?.text = MinistryGroupArray[indexPath.row]
cell.textHiddenUserTagName?.text = OneSignalUserTagArray[indexPath.row]
// set the custom cell's UISwitch.
OneSignal.getTags({ tags in
print("tags - \(tags!)")
self.OneSignalUserTags = String(describing: tags)
print("OneSignalUserTags, from within the OneSignal func, = \(self.OneSignalUserTags)")
if self.OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
}, onFailure: { error in
print("Error getting tags - \(String(describing: error?.localizedDescription))")
// errorWithDomain - OneSignalError
// code - HTTP error code from the OneSignal server
// userInfo - JSON OneSignal responded with
})
viewWillAppear(true)
return cell
}
}
In the above portion of the VC code, this part (below) is what is functioning but apparently not in a way the uses threading properly:
if OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
It's not entirely clear what your code is doing, but there seems to be a few things that need sorting out, that will help you solve your problem.
1) Improve the naming of your objects. This helps others see what's going on when asking questions.
Don't call your cell CustomTableViewCell - call it, say, MinistryCell or something that represents the data its displaying. Rather than textLabelMinistryGroupList and textHiddenUserTagName tree ministryGroup and userTagName etc.
2) Let the cell populate itself. Make your IBOutlets in your cell private so you can't assign to them directly in your view controller. This is a bad habit!
3) Create an object (Ministry, say) that corresponds to the data you're assigning to the cell. Assign this to the cell and let the cell assign to its Outlets.
4) Never call viewWillAppear, or anything like it! These are called by the system.
You'll end up with something like this:
In your view controller
struct Ministry {
let group: String
let userTag: String
var tagExists: Bool?
}
You should create an array var ministries: [Ministry] and populate it at the start, rather than dealing with MinistryGroupArray and OneSignalUserTagArray separately.
In your cell
class MinistryCell: UITableViewCell {
#IBOutlet private weak var ministryGroup: UILabel!
#IBOutlet private weak var userTagName: UILabel!
#IBOutlet private weak var switch: UISwitch!
var ministry: Ministry? {
didSet {
ministryGroup.text = ministry?.group
userTagName.text = ministry?.userTag
if let tagExists = ministry?.tagExists {
switch.isEnabled = false
switch.isOn = tagExists
} else {
// We don't know the current state - disable the switch?
switch.isEnabled = false
}
}
}
}
Then you dataSource method will look like…
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! MinistryCell
let ministry = ministries[indexPath.row]
cell.ministry = ministry
if ministry.tagExists == nil {
OneSignal.getTags { tags in
// Success - so update the corresponding ministry.tagExists
// then reload the cell at this indexPath
}, onFailure: { error in
print("Error")
})
}
return cell
}
Related
I have a tableview consisting of 2 cells and each cell has a textfield. I'd like to hold the text in textfield in an another variable and append it into an array but it just save the second cell's textfield's text into the variable and doesn't append the array.
Here is cellForRowAt code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == queryTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: queryCellReuseIdentifier,
for: indexPath) as! queryCell
cell.selectionStyle = UITableViewCell.SelectionStyle.gray
queryTextField = UITextField(frame: CGRect(x: 20, y: 0, width: 300, height: 20))
queryTextField.delegate = self
queryTextField.placeholder = "Soruyu buraya giriniz"
queryTextField.font = UIFont.systemFont(ofSize: 15)
cell.addSubview(queryTextField)
return cell
}
Here is my related function :
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = queryTextField.text!
queryArray.append(temp)
print(temp)
}
if your project is non-storyboard approach, then I would say, it would be more easier and flexible to take control of each type and data and action since sometimes things not quite flexible with storyboard only.. . as you create, even a single UIElement, UI conforms to those properties, methods and actions which you define, means fully customized code - custom defined code base..
now to your question, according to what you did, probably, your textfield text is overwritten to blank, losing the previously entered value every time the cell is dequeued, cell is generated fresh again for use every time " dequeue cell " method is executed, if you want to persist your previous value, I would say, " remind the dequeued cell that it had some value before being re- dequeued again " means:
declare your global array or dict.
dequeue your cell and setup your cell properties
3 after cell is dequeued, do something like this, and your cells previous value is saved even after the cell is dequeued.
example, common approach:
struct DefaultInfo {
var string: String?
var type: SomeData? //optional
}
let array: [DefaultInfo] = []
var array = self.array[indexPath.row]
// after cell is dequeued:
switch array.type {
case .someCase:
let string = cell.textField.text
array.string = string
cell.textField.text = array.string
//print(array.string!)
// now you should be able to see your input after cell is
//renewed.. . try it.. .
default: break
}
It seems like you have an independant variable queryTextField which is overriden when you create the second cell.
Also, in textFieldDidEndEditing try accessing textField instead of queryTextField, like this:
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = textField.text!
queryArray.append(temp)
print(temp)
}
Responding to this:
I need every cell has its textfield and I need to be able to save
their texts into an array. After that I am going to send them to web
service. To sum up, they will be my parameters.
You don't need to have global queryTextField just for that. You can remove this variable, if that's its only goal.
Do you need to send web request on some trigger? Like button tap. I assume, yes.
Since theoretically not all your cells are visible at the same time (e.g. when thay do not fit the screen), it's bad idea to try and track texts in text fields. Instead, you need some kind of model to store all texts (paired with indeces, maybe). The simplest would be dictionary where key is cell identifier (e.g. indexPath) and value is the text.
In textFieldDidEndEditing you can report the changes to the view controller. For this you need to assign the cell as the delegate for its text field. And view controller - as the delegate for the cell. In textFieldDidEndEditing cell would be calling view controller delegate method to report the text change, passing (for example) self and text as parameters. View controller would then be able to find its index path and store the text in the model (dictionary).
On trigger (button click?) view controller will be able to build parameters from the model (dictionary).
Consider this pseudocode as a direction:
cellForRow {
...
cell = ...
let textField = ...
textField.delegate = cell
cell.addSubview(textField)
cell.delegate = self
}
In cell:
textFieldDidEndEditing(textField: UITextField) {
delegate?.report(text: textField.text ?? "", in: self)
}
In view controller:
report(text: String, in cell: UITableViewCell) {
let indexPath = tableView.indexPath(of: cell)
model[indexPath] = text
}
I have a UITabBarController (as initial view controller) which checks connectivity status of the device. Everytime the connectivity status changes, a checkmark in a child UITableViewController cell (.accessoryType) should be set (.checkmark) or removed (.none)
Code in Tab Bar Controller:
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
let tvc = InfoTableViewController()
if path.status == .satisfied {
// set .checkmark in UITableViewController
let cell = tvc.statusOnlineCell
print("cell :", cell)
} else {
// set .none in UITableViewController
}
}
let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)
Outlet in UITableViewController:
#IBOutlet weak var statusOnlineCell: UITableViewCell!
I can change the accessory type from inside the UITableViewController class using .checkmark and .none.
statusOnlineCell.accessoryType = .checkmark
statusOnlineCell.accessoryType = .none
All fine so far!
However as soon as I try to access the UITableView.statusOnlineCell from UITabBar, I get nil. Hence, I cannot change its property outside the UITableViewController.
I saw 3 possible approaches:
A global variable, which reflects the online status. I could use the UITableView.viewDidAppear() method to change the statusOnlineCell accessory type. This works, but only if UITableView is not shown (only if another than UITableView is shown). If the UITableView is shown and I change the connectivity status, the view is not reloaded and I didn't find any way to achieve this. Is this possible?
Find a possibility to change the accessory type of UITableView.statusOnlineCell from the UITabBarController. Accessing the outlook returned in nil. Why is that? On top, after the accessory type would have changed, I would need to reload the view (for the case that the UITableView was active while changing connectivity status).
Is there any kind of (unknown to me) method which fires, when an object's property changed (à la needReload())? This would be too good to be true I believe.
To summarize - I need code to change the accessory type of a tableview cell, depending on the connectivity status, even whith this tableview being visible.
I read some tutorials and stackexchange articles, Google, ... but none did the job.
This was my top candidate, but I didn't manager to apply these examples to my situation.
https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/#back-delegation
I didn't want to use notifications since not really appropriate.
Any hint would be sufficient. Thanks in advance.
----- EDIT (14:52 UTC) ----- (requested by #vadian)
I added the (testing) code in the UITabBarController. The result of cell shows nil. So I cannot directly address the cell in UITableView from UITabBarController.
The UITableView doesn't have any related code yet since I directly address the property statusOnlineCell from UITabBarController in order to change its accessory type.
This approach is option 2. (of my 3 possibilities mentioned above).
I didn't find a solution, but a workaround.
Why no solution : the cell object was always nil if called from another class. Hence no chance to change it's property to .checkmark or .none. This was basically my key problem and unknown to me.
The workaround : a delegate! I declared the class, holding the cell, as delegate for the connectivity status change. Like that, the checkmark gets set or is removed from the cell, independantly whether the page is in view or not.
This runs when the app starts (UITabBarController):
protocol StatusOnlineChangedDelegate {
func updateStatusOnline(_ online: Bool)
}
class TabBarController: UITabBarController {
var socDelegate: StatusOnlineChangedDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
self.socDelegate?.updateStatusOnline(true)
} else {
self.socDelegate?.updateStatusOnline(false)
}
}
let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)
}
}
The UITableViewController holding the cell looks like this:
class InfoTableViewController: UITableViewController, StatusOnlineChangedDelegate {
#IBOutlet weak var statusOnlineCell: UITableViewCell!
override func viewDidLoad() {
super.viewDidLoad()
TabBarController().socDelegate = self
}
func updateStatusOnline(_ online: Bool) {
DispatchQueue.main.async {
self.statusOnlineCell.accessoryType = (online ? .checkmark : .none)
}
}
}
Runs perfectly as desired.
First let me say this seems to be a common question on SO and I've read through every post I could find from Swift to Obj-C. I tried a bunch of different things over the last 9 hrs but my problem still exists.
I have a vc (vc1) with a collectionView in it. Inside the collectionView I have a custom cell with a label and an imageView inside of it. Inside cellForItem I have a property that is also inside the the custom cell and when the property gets set from datasource[indePath.item] there is a property observer inside the cell that sets data for the label and imageView.
There is a button in vc1 that pushes on vc2, if a user chooses something from vc2 it gets passed back to vc1 via a delegate. vc2 gets popped.
The correct data always gets passed back (I checked multiple times in the debugger).
The problem is if vc1 has an existing cell in it, when the new data is added to the data source, after I reload the collectionView, the label data from that first cell now shows on the label in new cell and the data from the new cell now shows on the label from old cell.
I've tried everything from prepareToReuse to removing the label but for some reason only the cell's label data gets confused. The odd thing is sometimes the label updates correctly and other times it doesn't? The imageView ALWAYS shows the correct image and I never have any problems even when the label data is incorrect. The 2 model objects that are inside the datasource are always in their correct index position with the correct information.
What could be the problem?
vc1: UIViewController, CollectionViewDataSource & Delegate {
var datasource = [MyModel]() // has 1 item in it from viewDidLoad
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}
// delegate method from vc2
func appendNewDataFromVC2(myModel: MyModel) {
// show spinner
datasource.append(myModel) // now has 2 items in it
// now that new data is added I have to make a dip to fb for some additional information
firebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] else { }
for myModel in self.datasource {
myModel.someValue = dict["someValue"] as? String
}
// I added the gcd timer just to give the loop time to finish just to see if it made a difference
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
self.datasource.sort { return $0.postDate > $1.postDate } // Even though this sorts correctly I also tried commenting this out but no difference
self.collectionView.reloadData()
// I also tried to update the layout
self.collectionView.layoutIfNeeded()
// remove spinner
}
})
}
}
CustomCell Below. This is a much more simplified version of what's inside the myModel property observer. The data that shows in the label is dependent on other data and there are a few conditionals that determine it. Adding all of that inside cellForItem would create a bunch of code that's why I didn't update the data it in there (or add it here) and choose to do it inside the cell instead. But as I said earlier, when I check the data it is always 100% correct. The property observer always works correctly.
CustomCell: UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let priceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var someBoolProperty = false
var myModel: MyModel? {
didSet {
someBoolProperty = true
// I read an answer that said try to update the label on the main thread but no difference. I tried with and without the DispatchQueue
DispatchQueue.main.async { [weak self] in
self?.priceLabel.text = myModel.price!
self?.priceLabel.layoutIfNeeded() // tried with and without this
}
let url = URL(string: myModel.urlStr!)
imageView.sd_setImage(with: url!, placeholderImage: UIImage(named: "placeholder"))
// set imageView and priceLabel anchors
addSubview(imageView)
addSubview(priceLabel)
self.layoutIfNeeded() // tried with and without this
}
}
override func prepareForReuse() {
super.prepareForReuse()
// even though Apple recommends not to clean up ui elements in here, I still tried it to no success
priceLabel.text = ""
priceLabel.layoutIfNeeded() // tried with and without this
self.layoutIfNeeded() // tried with and without this
// I also tried removing the label with and without the 3 lines above
for view in self.subviews {
if view.isKind(of: UILabel.self) {
view.removeFromSuperview()
}
}
}
func cleanUpElements() {
priceLabel.text = ""
imageView.image = nil
}
}
I added 1 breakpoint for everywhere I added priceLabel.text = "" (3 total) and once the collectionView reloads the break points always get hit 6 times (3 times for the 2 objects in the datasource).The 1st time in prepareForReuse, the 2nd time in cellForItem, and the 3rd time in cleanUpElements()
Turns out I had to reset a property inside the cell. Even though the cells were being reused and the priceLabel.text was getting cleared, the property was still maintaining it's old bool value. Once I reset it via cellForItem the problem went away.
10 hrs for that, smh
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.someBoolProperty = false
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}
I have a collection view in which each cell possess the ability to be interacted with by the user. Each cell has a like button and a number of likes label. When the button is pressed, the button should turn cyan, and the label (which holds the number of likes) should increment. This setup currently works. However, when I scroll through the collection view and scroll back, the button reverts to its original color (white) and the label decrements down to its original value. I have heard of an ostensibly helpful method called prepareForReuse(), but perhaps I'm not using it correctly. Here is my code:
Here is the array which holds all the cells
var objects = [LikableObject]()
Here is the class definition for these objects
class LikableObject {
var numOfLikes: Int?
var isLikedByUser: Bool?
init(numOfLikes: Int, isLikedByUser: Bool) {
self.numOfLikes = numOfLikes
self.isLikedByUser = isLikedByUser
}
}
Mind you, there is more functionality present in this object, but they are irrelevant for the purposes of this question. One important thing to be noted is that the data for each cell are grabbed using an API. I'm using Alamofire to make requests to an API that will bring back the information for the numOfLikes and isLikedByUser properties for each cell.
Here is how I load up each cell using the collection view's delegate method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ObjectCell", for: indexPath) as! ObjectCell
cell.configureCell(
isLikedByUser: objects[indexPath.row].isLikedByUser!,
numOfLikes: objects[indexPath.row].numOfLikes!,
)
return cell
}
The ObjectCell class has these three fields:
var isLikedByUser: Bool?
#IBOutlet weak var numOfLikes: UILabel!
#IBOutlet weak var likeBtn: UIButton!
And that configureCell() method, which belongs to the cell class, is here:
public func configureCell(numOfLikes: Int, isLikedByUser: Bool) {
self.isLikedByUser = isLikedByUser
self.numOfLikes.text = String(numOfLikes)
if isLikedByUser {
self.likeBtn.setFATitleColor(color: UIColor.cyan, forState: .normal)
} else {
self.likeBtn.setFATitleColor(color: UIColor.white, forState: .normal)
}
}
And lastly, the prepareForReuse() method is here:
override func prepareForReuse() {
if isLikedByUser! {
self.likeBtn.setTitleColor(UIColor.cyan, for: .normal)
} else {
self.likeBtn.setTitleColor(UIColor.white, for: .normal)
}
}
This doesn't work. And even if it did, I still don't know a way to keep the numOfLikes label from decrementing, or if it should anyway. I'm speculating that a big part of this problem is that I'm not using the prepareForReuse() method correctly... Any help is appreciated, thank you.
prepareForReuse is not the place to modify the cell, as the name states, you "only" have to prepare it for reuse. if you changed something (for example isHidden property of a view), you just have to change them back to initial state.
What you should do though, you can implement didSet for isLikedByUser inside the cell, and apply your modifications to likeBtn in there. (this is of-course the fast solution)
Long solution: It's an anti-pattern that your cell has a property named isLikedByUser, TableViewCell is a View and in all architectures, Views should be as dumb as they can about business logic. the right way is to apply these modifications in configure-cell method which is implemented in ViewController.
If you feel you'll reuse this cell in different viewControllers a lot, at least defined it by a protocol and talk to your cell through that protocol. This way you'll have a more reusable and maintainable code.
Currently all of this is good , the only missing part is cell reusing , you have to reflect the changes in the number of likes to your model array
class ObjectCell:UICollectionViewCell {
var myObject:LikableObject!
}
In cellForRowAt
cell.myObject = objects[indexPath.row]
Now inside cell custom class you have the object reflect any change to it , sure you can use delegate / callback or any observation technique
The prepareForResuse isn't needed here.
You do need to update the model underlying the tableview. One way to verify this is with mock data that is pre-liked and see if that data displays properly.
In my swift app I have a UITableView with one static cell and many dynamic cells.
Static cell contains different fields, such as labels, map (taken from MapKit) and a button, that indicates whether user voted up or not.
Now, when user presses the button, I want to change its color, possibly without refreshing anything else.
So far my code looks like this:
var currentUserVote:Int = 0
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).row == 0 {
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
fetchScore(cell.score)
let voteUpImage = UIImage(named: "voteUp");
let tintedVoteUpImage = voteUpImage?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
cell.voteUpButton.setImage(tintedVoteUpImage, for: UIControlState())
checkUsersVote() { responseObject in
if(responseObject == 1) {
cell.voteUpButton.tintColor = orangeColor
} else if (responseObject == -1){
cell.voteUpButton.tintColor = greyColor
} else {
cell.voteUpButton.tintColor = greyColor
}
self.currentUserVote = responseObject
}
//map handling:
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnLocation(initialLocation, map: cell.mapView, radius: regionRadius)
//cell.mapView.isScrollEnabled = false
cell.mapView.delegate = self
.
.
.
return cell
} else {
//handle dynamic cells
}
}
So in the method above I'm checking if user voted already and based on that I'm setting different color on the button. I'm also centering the map on a specific point.
Now, since it's a static cell, I connected IBAction outlet to that button:
#IBAction func voteUpButtonAction(_ sender: AnyObject) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
}
and the vote method works as follows:
func vote(_ vote: Int){
let indexPath = IndexPath(row: 0, section: 0)
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
switch(vote) {
case 1:
cell.voteUpButton.tintColor = orangeColor
case 0:
cell.voteUpButton.tintColor = greyColor
case -1:
cell.voteUpButton.tintColor = greyColor
default:
cell.voteUpButton.tintColor = greyColor
}
tview.beginUpdates()
tview.reloadRows(at: [indexPath], with: .automatic)
tview.endUpdates()
currentUserVote = vote
//sending vote to my backend
}
My problem is, that when user taps the button, he invokes the method vote, then - based on the vote, the button changes color, but immediately after that method cellForRow is called and it changes the color of the button again. Also, it refreshes the map that's inside of it.
What I want to achieve is that when user taps the button, it should immediately change its color and that's it. Map stays untouched and the button is not changed again from cellForRow.
Is there a way of refreshing only that particular button without calling again cellForRow?
First of all, you confuse static and dynamic cells. You can use static cells only in the UITableViewController and you can't use static and dynamic cell at the same time.
I strongly recommend you not to use cell for storing map and button. All elements from the cell will be released after scrolling it beyond the screen.
I can advise you use TableViewHeaderView for this task. In this case you will be able set button and map view as #IBOutlet.
(See more about adding tableview headerView. You can also set it from interface builder.)
Another way is change tableView.contentInset and set your view with map and button as subview to tableView. This method is used when you need create Stretchy Headers.
It should be quite easy, simply do it in your button handler. The sender argument there is the button object that caused the action. When you were connecting it from IB there was a dropdown to select sender type, you may have missed it and the whole thing would have been obvious with UIButton type there.
Simply change your handler like this :
#IBAction func voteUpButtonAction(_ sender: UIButton) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
sender.backgroundColor = yourFavouriteColor
}
Another approach would be to create an IBOutlet for your button, since its from a static cell, and then you would be able to reference it from any place in your view controller.
In this call:
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I see it calls checkUsersVote() which I'm guessing should get the updated value set in the vote() call. Could the problem be that you aren't doing this
currentUserVote = vote
until after reloadRows() is called?