Here is my code. I want to learn how to save my dataSource for the like button of each cell. So that when the user leaves the app and returns later, the same cells that were liked remain liked.
This is new code from my last post. Since I don't want to keep making new arrays, I can try and locally save my dataSource variable which stores each cells Status of being liked or not.
ViewController -
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var dataSource: [TableModel] = []
var updatedCell = TableViewCell()
var updatedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.dataSource = Array(repeating: TableModel(isLiked: false), count: 8)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
#IBAction func buttonSelected(_ sender: Any) {
// Update Cell for which UIButton (Like Button) was tapped.
dataSource[(sender as AnyObject).tag].isLiked = !dataSource[(sender as AnyObject).tag].isLiked
let indexPath = IndexPath(row: (sender as AnyObject).tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Saved updatedCell & updatedIndex variables for delegate pattern. To Update this VC's cells data when edited on secondVC (moreInfoViewController) in a way, made them accessible outside this function
// To Get Specific TableView Cell the user is interacting with.
updatedCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
updatedIndex = indexPath.row
// Go to Second VC and Send cell tapped data to next view
let vc = (storyboard?.instantiateViewController(identifier: "secondVC") as? moreInfoViewController)!
vc.delegate = self
// Get Status of Liked Button in the cell the user tapped and display if the user liked it previously in the SecondVC
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// print("Liked")
vc.isLiked = true
} else {
// print("Not Liked")
vc.isLiked = false
}
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
// cell.moreBtn.isUserInteractionEnabled = false
cell.likeBtn.tag = indexPath.row
// cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
// Get Each Cell Liked Button Status and display if the user liked or not Liked each cell
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// User liked the post
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// User Unliked the post
cell.likeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
return cell
}
}
// Conform VC to protocol (VC2Delegate) located in "Structs.swift" File
extension ViewController: VC2Delegate {
func likeStatusDidChange(_ vc2: moreInfoViewController, to title: Bool) {
// set the text of the table cell here...
dataSource[updatedIndex].isLiked = !dataSource[updatedIndex].isLiked
let indexPath = IndexPath(row: updatedIndex, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
moreInfoViewController -
import UIKit
class moreInfoViewController: UIViewController {
#IBOutlet var backBtn: UIButton!
#IBOutlet var titleLabel: UILabel!
#IBOutlet var locationLabel: UILabel!
#IBOutlet var theImage: UIImageView!
#IBOutlet var mainlikeBtn: UIButton!
#IBOutlet var mainTypeLbl: UILabel!
var currentID:String = ""
var isLiked = Bool()
weak var delegate: VC2Delegate?
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.navigationController?.setNavigationBarHidden(true, animated: true)
styles()
// "isLiked" variable to display whether or user liked this event
if (isLiked == true) {
// is Liked
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// Not Liked
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
}
// Heart/Like Button Action. User can like event in this VC with this button and it will tell the firstVC (ViewController) to update "Like Status" there also
#IBAction func likeBtnAction(_ sender: Any) {
if (isLiked == true) {
// is Liked
isLiked = false
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
} else {
isLiked = true
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
}
// When User interacts with like Button, this function gets called that tells the firstVC (ViewController) to update as well.
// likeStatusDidChange function is located at the bottom of the (ViewController) with extension ViewController.
delegate?.likeStatusDidChange(self, to: true)
}
// Go Back To FirstVC (ViewController)
#IBAction func previousVC(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func styles() {
titleLabel.numberOfLines = 1
titleLabel.adjustsFontSizeToFitWidth = true
locationLabel.numberOfLines = 1
locationLabel.adjustsFontSizeToFitWidth = true
backBtn.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
theImage.layer.borderColor = UIColor.black.cgColor
theImage.layer.borderWidth = 2
theImage.layer.cornerRadius = 10
}
}
When you get array from userdefaults it gives you Any type array so you need to cast it to Int array and check it nil or not before you use. So you can use this code block.
if let savedDataKey = UserDefaults.standard.array(forKey: "savedDataKey") as? [Int] {
print(savedDataKey)
}
Related
I want to save the value of filled button(●) into the array "q.answer[indexPath.row]" about each question "q.question[indexPath.row]).
currentQuizButtonIndex is currently renewed every time when ◯ changes to ● by tapping. However, I have no idea how to save in to variable q which is declared in TableViewController.
View Controller display
Code about QuizCell.swift (TableCell which is about 5 buttons and UIlabel.)
import UIKit
import Foundation
protocol QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int)
}
class QuizCell: UITableViewCell {
var currentQuizButtonIndex: Int = 0 {
didSet {
let value = self.currentQuizButtonIndex
self.updateCurrentQuizButton(value)
if let delegate = self.delegate {
delegate.quizCellDidChangeCurrentButtonIndex(self, index: value)
}
}
}
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet var answerButtons: [UIButton]!
var delegate: QuizCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
//print("ここまできてるか確認")
// Initialization code
}
#IBAction func didTapQuizButton(_ sender: UIButton) {
if let index = self.answerButtons.firstIndex(of: sender){
self.currentQuizButtonIndex = index
delegate?.quizCellDidChangeCurrentButtonIndex(self, index: index)
print(index)
}
}
private func updateCurrentQuizButton(_ currentIndex: Int){
for (index, answerButton) in self.answerButtons.enumerated(){
if index == currentIndex {
answerButton.setTitle("●", for: .normal)
} else {
answerButton.setTitle("○", for: .normal)
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Code about View Controller
import UIKit
class AnswerQuizViewController: UIViewController, UITableViewDelegate {
var q: QuestionSeries!
#IBOutlet weak var quizTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
quizTableView.dataSource = self
quizTableView.delegate = self
// cell xibファイルを使うときは書く必要があるやつ。
// quizTableView.register(UINib(nibName: K.Cells.QuizCellNibName, bundle: nil), forCellReuseIdentifier: K.Cells.QuizCellIdentifier)
quizTableView.register(UINib(nibName: "QuizCell", bundle: nil), forCellReuseIdentifier: "QuizCellIdentifier")
// Do any additional setup after loading the view.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// // Get the new view controller using segue.destination.
// // Pass the selected object to the new view controller.
//// if segue.identifier == K.Segue.checkResult {
//// let resultViewController = segue.destination as! ResultViewController
//// answerQuizViewController.q =
//// print(answerQuizViewController.q)
// }
}
// MARK: - quizTableViewのアレンジ
extension AnswerQuizViewController: UITableViewDataSource, QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int) {
if let indexPath = self.quizTableView.indexPath(for: cell){
self.q.question[indexPath.row].answer = index
print(index)
}else{
print("ここきてます")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return q.question.count
//print(q.question.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let question = q.question[indexPath.row]
let cell = quizTableView.dequeueReusableCell(withIdentifier: K.Cells.QuizCellIdentifier, for: indexPath) as! QuizCell
cell.questionLabel.text = question.text
// print(question.text)
return cell
}
}
It is also helpful if you have any idea of implementing this by alternative way.
Thanks.
How about you create a static array and store your data into that array.
when the button is tapped you can append it into that static array.
Create a new file. Just a basic "Swift file".
struct structName {
static var qArray: [String] = []
}
Then append data by:
structName.q.append()
Finally get your data trough:
structName.q[index]
The summary of my problem is; when the tableView.reloadData() is called upon firestore's local changes on the first time the view loads up it works and updates as it supposed to. However, after I switch forth and back with other viewControllers, although initially on viewDidAppear() tableView get's reloaded, upon local changes it no longer does so.
I've included a simpler version of my project to better explain and to make it reproducable;
Class Item
let db = Firestore.firestore()
static var list = [String:[String:Any]]()
static var listenerSet = Bool()
static var listener: ListenerRegistration!
func setListener(completion: #escaping (String) -> Void) {
if !Item.listenerSet {
print("Attaching item document listener.")
Item.listener = db.collection("Items").document("default").addSnapshotListener({ (document, error) in
let source = document!.metadata.hasPendingWrites ? "Local" : "Server"
print("Updating item data from the \(source).")
Item.listenerSet = true
Item.list = document?.get("List") as! [String:[String:Any]]
completion("Item data is set")
})
} else {
completion("Item listener already exists")
}
}
func add(itemID:String) {
let itemRef = db.collection("Items").document("default")
itemRef.setData([
"List": [
itemID : [
"Count": FieldValue.increment(1.0),
]]
], merge:true)
}
FirstViewController:UIViewController
let item = Item()
var itemList = [[String:Any]]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var switchBar: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
// Setting the table views
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
print("View Did appear") // prints OK
item.setListener() { (result) in
print(result)
loadData()
}
}
func loadData() {
itemList.removeAll()
var counter = 0
for (id,data) in Item.list {
itemList.append(data)
itemList[counter]["ID"] = id
counter += 1
}
tableView.reloadData()
}
#IBAction func switchBarChanged(_ sender: UISegmentedControl) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
switch switchBar.selectedSegmentIndex {
case 1:
let vc = storyBoard.instantiateViewController(identifier: "secondViewController")
show(vc, sender: self)
default:
break
}
}
Extension FirstViewController: UITableViewDataSource,UITableViewDelegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("These are items:\(itemList)")
print(itemList.count)
print(tableView.bounds.height)
return itemList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! ItemCell
let itemID = itemList[indexPath.row]["ID"] as! String
let itemName = itemList[indexPath.row]["Name"] as! String
let itemCount = itemList[indexPath.row]["Count"] as! Double
cell.itemID = itemID
cell.itemName.text = itemName
cell.itemCount.text = String(itemCount)
print("Items within the tableView \(itemList)") // prints only in the first run whenever there is an upload, or when refreshed.
return cell
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let add = UIContextualAction(style: .normal, title: "Add", handler: { (action, view, completionHandler) in
item.add(itemID:(itemList[indexPath.row]["ID"] as! String))
completionHandler(true)
})
add.image = UIImage(systemName: "plus")
let configuration = UISwipeActionsConfiguration(actions: [add])
configuration.performsFirstActionWithFullSwipe = true
return configuration
}
Class ItemCell:UITableViewCell
var itemID = String()
#IBOutlet weak var itemName: UILabel!
#IBOutlet weak var itemCount: UILabel!
SecondViewController:UIViewController
#IBOutlet weak var switchBar: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func switchBarChanged(_ sender: UISegmentedControl) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
switch switchBar.selectedSegmentIndex {
case 0:
let vc = storyBoard.instantiateViewController(identifier: "firstViewController")
show(vc, sender: self)
default:
break
}
}
Now this is the flow I'm having issue on;
Application loads, FirstViewController loads, viewDidAppear kicks in. tableView loads.
When swiped right, the value in firestore server increments by one, snapshot listener kicks in, tableview reloads and everything works as it supposed to be.
After I switch to SecondViewController and come back to FirstViewController, viewDidAppear kicks in tableView loads.
This time however, when I swipe right again, the value in firestore server still increments by one and the snapshot listener kicks in, updating the Item.list. static var ItemList gets updated, and tableView does reload, however this time, only numberOfRowsInSection method works as it prints the count and updated itemList, then nothing happens cellForRowAt doesn't work and rows don't get updated.
Now at this stage if I go SecondViewController and comeback to FirstViewController the rows are updated, but when swiped right still no avail.
What am I missing here ? Thanks all for your replies.
recently in the search VC of my app I have been having a major problem with the contraints for weeks. I've redone them 4 times now with no improvement. My tableview cell is not complex -- an imageView located at the left and two labels on top of each other. Most cells size fine. The only thing is that(without pattern) two cells are randomly messed up(the labels and imageView move way out of place). You can see here:
Here is a picture of the constraints for the vc:
The strange thing is that this sometimes occurs when I toggle the scopeButtons up top. I looked in my code to see if I could fix this when the scopeButton reloads the tableview, but I could not find a problematic instance. My code is below:
import UIKit
import Firebase
import Kingfisher
class SearchPostsController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var idArray:[String] = []
var detailViewController: DetailViewController? = nil
var candies = [Person]()
var filteredCandies = [Person]()
let searchController = UISearchController(searchResultsController: nil)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = false
//initiate the search bar that appears up top when view is segued to
}
if let selectionIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectionIndexPath, animated: animated)
}
self.tableView.reloadData()
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference()
.child("\(UserData().mySchool!)/posts")
.queryOrderedByKey()//keys were out of order, so we have to use this to help
.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.childrenCount)
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.key)
self.idArray.append(child.key)
}
var reversedNames = [String]()
for arrayIndex in 0..<self.idArray.count {
reversedNames.append(self.idArray[(self.idArray.count - 1) - arrayIndex])
//reverse names so we dont have to sort the cells by date
}
for x in reversedNames{
self.searchNames(id: x)//get names from the ids here, tada!!!
self.tableView.reloadData()
}
})
//self.tableView.reloadData()//without this, the results wouldnt show up right away
searchController.searchBar.setScopeBarButtonTitleTextAttributes([NSAttributedStringKey.foregroundColor.rawValue: UIColor.white], for: .normal)
searchController.searchBar.scopeButtonTitles = ["Posts", "Users"]
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search posts or usernames"
searchController.searchBar.showsScopeBar = true
navigationItem.searchController = searchController
definesPresentationContext = true
if let splitViewController = splitViewController {
let controllers = splitViewController.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 74
}
// override func viewWillDisappear(_ animated: Bool) {
// candies.removeAll()
// }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DAY69:GRADUATION DAY", for: indexPath) as! SeachCell
cell.cellImageVIew.frame.size.width = 48
cell.cellImageVIew.frame.size.height = 48
let personUser: Person
if isFiltering() {
personUser = filteredCandies[indexPath.row]
} else {
personUser = candies[indexPath.row]
}
//PERSON USER IS IMPORTANT!!!!! ^^^
cell.nameLabel.text = personUser.name.removingPercentEncoding
cell.messageLabel.text = personUser.category.removingPercentEncoding
let name = personUser.name
Database.database().reference().child("users/\(name)/profileImageURL").observe(.value, with: { (snapshot) in
let profURL = "\(snapshot.value!)"
let profIRL = URL(string: profURL)
//set up imageview
cell.cellImageVIew.layer.borderWidth = 1
cell.cellImageVIew.layer.masksToBounds = false
cell.cellImageVIew.layer.borderColor = UIColor.black.cgColor
cell.cellImageVIew.layer.cornerRadius = cell.cellImageVIew.frame.height/2
cell.cellImageVIew.clipsToBounds = true
cell.cellImageVIew.contentMode = .scaleAspectFill
cell.cellImageVIew.kf.indicatorType = .activity
cell.cellImageVIew.kf.setImage(with: profIRL)
})
//TODO: make an extension of imageview to do all this for me. It's getting to be ridiculous
return cell
}
func searchNames(id: String){
// var message = String()
// var name = String()
Database.database().reference().child("\(UserData().mySchool!)/posts/\(id)/message").observe(.value, with: { (snapshot) in
// message = snapshot.value as! String
Database.database().reference().child("\(UserData().mySchool!)/posts").child("\(id)/username").observe(.value, with: { (username) in
// name = username.value as! String
let user = Person(category: "\(snapshot.value!)", name: "\(username.value!)", id: id)
self.candies.append(user)
print( "\(snapshot.value!)", "\(username.value!)")
self.tableView.reloadData()
})
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredCandies.count
}
return candies.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.estimatedRowHeight = 74.0
tableView.rowHeight = UITableViewAutomaticDimension
return UITableViewAutomaticDimension
}
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// return 74
// }
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "DAY69:GRADUATION DAY", for: indexPath) as! SeachCell
cell.cellImageVIew.frame.size.width = 48
cell.cellImageVIew.frame.size.height = 48
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles?[searchBar.selectedScopeButtonIndex]
if scope == "Users"{
let username = candies[indexPath.row].name
print(username)
self.tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "userClicked", sender: username)
}
if scope == "Posts"{
let post = candies[indexPath.row].category
let user = candies[indexPath.row].name
let id = candies[indexPath.row].id
print(post)
let defaults = UserDefaults.standard
defaults.set(id, forKey: "ID")
let def2 = UserDefaults.standard
def2.set(post, forKey: "Post")
def2.set(user, forKey: "USER")
self.tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "postCellTapped", sender: nil)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//__________tbv methods above________________________________________________
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredCandies = candies.filter({(candy : Person) -> Bool in
let doesCategoryMatch = (scope == "Posts") || (scope == "Users")
print(searchText)
if searchBarIsEmpty() {
return doesCategoryMatch
}
if scope == "Users"{
return doesCategoryMatch && candy.name.lowercased().contains(searchText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!.lowercased())
}
else{
return doesCategoryMatch && candy.category.lowercased().contains(searchText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!.lowercased())
}
})
tableView.reloadData()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let personalUser: Person
if isFiltering() {
personalUser = filteredCandies[indexPath.row]
} else {
personalUser = candies[indexPath.row]
}
}
}
if segue.identifier == "userClicked" {
if let nextView = segue.destination as? UserProfileController {
nextView.selectedUser = "\(sender!)"
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func isFiltering() -> Bool {
let searchBarScopeIsFiltering = searchController.searchBar.selectedScopeButtonIndex != 0
return searchController.isActive && (!searchBarIsEmpty() || searchBarScopeIsFiltering)
}
}
extension SearchPostsController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
}
extension SearchPostsController: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
Any help would be greatly appreciated in trying to fix the constraints! Thanks!
I am creating a Check List app and trying to update button event which is set images itself after clicked.
Here is my code:
protocol CustomeCellDelegate {
func cell(cell: UITableViewCell, updateTheButton button: UIButton)
}
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
}
#objc func recordButtonImage(_ button: UIButton) {
cellDelegate?.cell(cell: self, updateTheButton: button) // cell notify the button that touched.
}
override func awakeFromNib() {
checkBox.addTarget(self, action: #selector(recordButtonImage(_:)), for: UIControlEvents.touchUpInside)
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, CustomeCellDelegate {
#IBOutlet weak var tableView: UITableView!
var myImages: [UIImage?]!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
myImages = Array(repeatElement(nil, count: 50))
let nib = UINib(nibName: "Cells", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "Cells")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
// This function from protocol and it helps to update button images.
func cell(cell: UITableViewCell, updateTheButton button: UIButton) {
print("Delegate method Update Button Images.")
if let indexPath = tableView.indexPath(for: cell), let buttonImage = button.image(for: UIControlState.normal) {
myImages[indexPath.row] = buttonImage
}
}
I would like to update both events of the button that set image after checked and unchecked. Therefore, my table view can dequeue Reusable Cell and have those events updated.
But the result is just one event of button which is checked updated but not the unchecked one. The checked button still repeats whatever I do to uncheck it.
I think you don't need recordButtonImage method, you should call the delegate method from the button tapped action i.e. checkBoxAction, just like below
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
// cell notify the button that touched.
cellDelegate?.cell(cell: self, updateTheButton: button)
}
}
Also your data source method, doesn't tell the cell whether the button was pressed or not. It just sets the image but doesn't set the variable buttonPressed
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
The best way to handle UIButton is using its own state.
You need to hold current state value in one array. So you can keep the state in cellForRow and other things. By default set all state to false.
In CellForRow, just put below code:
YOUR_BUTTON.isSelected = aryState[indexPath.row] as! Bool
Set image for your button
YOUR_BUTTON.setImage(UIImage(named: NORMAL_IMAGE), for: .normal)
YOUR_BUTTON.setImage(UIImage(named: SELECTED_IMAGE), for: .selected)
Just change button state when it clicked:
#IBAction func checkBoxAction(_ sender: UIButton) {
let button = sender
button.isSelected = !button.isSelected
}
I'm currently working on an inventory app that will hold information for two entities. The first entity holds two attributes "site name" and "site address". second entity holds "itemName" "itemQuantity" and "item picture". I'm able to save the data to core data. I'm having issues with fetching the data right now. Does any one know how to pass data between the view controllers.
I'm uploading the site view controller, detail view controller, and my new item view controller.
class SiteTableViewController: UIViewController {
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
var sites : [Site]? = [Site]()
//variable to hold selected site
var selectedSite : Site?
let myCellIdentifier = "cell"
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
sites = [Site]()
reloadData()
automaticallyAdjustsScrollViewInsets = false
//let barButtonItem = UIBarButtonItem(barButtonSystemItem: .Compose, target: self, action: "addSite:")
//self.navigationItem.leftBarButtonItems = [barButtonItem]
}
//load new data into table
func reloadData() {
let fetchRequest = NSFetchRequest(entityName: "Site")
let sortDescriptor = NSSortDescriptor(key: "siteName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
sites = try self.managedContext.executeFetchRequest(fetchRequest) as! [Site]
tableView.reloadData()
} catch {
print("Error fetching objects")
}
tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
reloadData()
}
}
extension SiteTableViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(myCellIdentifier, forIndexPath: indexPath)
let site = sites![indexPath.row]
cell.textLabel?.text = site.valueForKey("siteName") as? String
cell.detailTextLabel?.text = site.valueForKey("siteAddress") as? String
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let logItemToDelete = sites![indexPath.row]
sites?.removeAtIndex(indexPath.row)
managedContext.deleteObject(logItemToDelete)
do {
try managedContext.save()
} catch {
print("Could not save or delete object")
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sites!.count
}
// func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//
// selectedSite = sites![indexPath.row]
//
// }
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailSegue" {
var nextVC = segue.destinationViewController as! DetailTableViewController
var selectedItem : Site = sites![self.tableView.indexPathForSelectedRow!.row] as Site
fetchSite = selectedItem.valueForKey("siteName") as? NSManagedObject
nextVC.valueToPass = selectedItem.siteName!
}
}
}
here is my detail view controller
import UIKit
import CoreData
public var fetchSite : NSManagedObject?
class DetailTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var holdSiteData: NSMutableArray = NSMutableArray()
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
#IBOutlet weak var tableView: UITableView!
var sites : [Site]!
//string to hold fetchResults controll
//open string to hold siteName
var valueToPass = ""
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "\(valueToPass)"
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
automaticallyAdjustsScrollViewInsets = false
tableView.estimatedRowHeight = 75.0
tableView.rowHeight = UITableViewAutomaticDimension
retrieveItemRelationship()
}
override func viewWillAppear(animated: Bool) {
retrieveItemRelationship()
}
func retrieveItemRelationship() {
// fetchSite.setValue(NSSet(keyCommands), forKey: "test")
let fetchRequest = NSFetchRequest(entityName: "SiteItem")
let sortDescriptors = NSSortDescriptor(key: "itemName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptors]
do {
let result = try fetchSite?.managedObjectContext?.executeRequest(fetchRequest)
} catch let error as NSError {
print(error)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
// let cellInfo = fetchedResultsController.objectAtIndexPath(indexPath) as! SiteInfo
cell.cellName?.text = "Test"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return holdSiteData.count
}
}
this is my last view to save the data to. I figured i need to pass the data here then associate the two entities so that I can save it correctly. For instance, for site name "Staples Center". I need to pull this over to the final view controller then save the information to this object.
import UIKit
import CoreData
class ItemNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//var managedObjectContext : NSManagedObjectContext!
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
//categories array for picker view
var categories = ["Building Materials", "Electrical", "Cleaning Products",
"Tools & Hardware", "Plumbing", "Paint", "Appliances", "Other"]
//textFields
#IBOutlet weak var itemNameTextField: UITextField!
#IBOutlet weak var quantityTextField: UITextField!
#IBOutlet weak var imageView: UIImageView!
//Label properties
#IBOutlet weak var newItemLabel: UILabel!
#IBOutlet weak var quantityLabel: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
//pickerLabel
#IBOutlet weak var pickerListIcon: UIPickerView!
//clicker property label and textlabel for number
#IBOutlet weak var increaseNumberClicker: UIStepper!
#IBOutlet weak var numberFieldClicker: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//label property customize view code
newItemLabel.layer.masksToBounds = true
newItemLabel.layer.borderWidth = 2.0
newItemLabel.layer.borderColor = UIColor.whiteColor().CGColor
newItemLabel.layer.cornerRadius = 6
//label property customize view code
quantityLabel.layer.masksToBounds = true
quantityLabel.layer.borderWidth = 2.0
quantityLabel.layer.borderColor = UIColor.whiteColor().CGColor
quantityLabel.layer.cornerRadius = 6
//lable property customize view code
categoryLabel.layer.masksToBounds = true
categoryLabel.layer.borderWidth = 3.0
categoryLabel.layer.borderColor = UIColor.whiteColor().CGColor
categoryLabel.layer.cornerRadius = 6
//label picker customize view
pickerListIcon.layer.masksToBounds = true
pickerListIcon.layer.borderWidth = 3.0
pickerListIcon.layer.borderColor = UIColor.whiteColor().CGColor
pickerListIcon.layer.cornerRadius = 6
pickerListIcon.layer.backgroundColor = UIColor.blackColor().CGColor
self.pickerListIcon.dataSource = self
self.pickerListIcon.delegate = self
numberFieldClicker.enabled = false
//UIStepper value
increaseNumberClicker.wraps = true
increaseNumberClicker.autorepeat = true
increaseNumberClicker.layer.masksToBounds = true
increaseNumberClicker.layer.borderWidth = 3.0
increaseNumberClicker.layer.borderColor = UIColor.whiteColor().CGColor
increaseNumberClicker.layer.cornerRadius = 6
increaseNumberClicker.layer.backgroundColor = UIColor.blackColor().CGColor
//below code declares a variable that inherits from UITapGesture
//this allows the image view to be an action
//*Make sure you enable user interaction in storyBoard very IMPORTANT!
let imageTapRecognizer = UITapGestureRecognizer(target: self, action: "enableImage")
self.imageView.addGestureRecognizer(imageTapRecognizer)
}
// below function allows the camera view to be accessible
func enableImage(){
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
let cameraView = UIImagePickerController()
cameraView.sourceType = UIImagePickerControllerSourceType.Camera
cameraView.delegate = self
self.presentViewController(cameraView, animated: true, completion: nil)
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
self.imageView.image = image
picker.dismissViewControllerAnimated(true, completion: nil)
}
//function used to increase UIStepper. increment by integers of one
#IBAction func clickerIncreaser(sender: UIStepper) {
numberFieldClicker.text = Int(sender.value).description
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return categories.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return categories[row]
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
//passing the categorories array into the new constant myCategoriesArray
let myCategoriesArray = categories[row]
let myTextColor = NSAttributedString(string: myCategoriesArray, attributes: [NSForegroundColorAttributeName: UIColor.whiteColor()])
return myTextColor
}
//save data to core data
#IBAction func saveButton(sender: AnyObject) {
if (itemNameTextField.text?.isEmpty)! {
let displayAlertController = UIAlertController(title: "Form Incomplete", message: "Please provide item with a name", preferredStyle: .Alert)
let okButton : UIAlertAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
displayAlertController.addAction(okButton)
self.presentViewController(displayAlertController, animated: true, completion: nil)
} else {
//declare site object and object
let siteInfoEntity = NSEntityDescription.entityForName("SiteItem", inManagedObjectContext: self.managedContext)
//declare item and item object
let siteObject = SiteItem(entity: siteInfoEntity!, insertIntoManagedObjectContext: self.managedContext)
//convert String to Integer
let quantityNumber: Int? = Int(quantityTextField.text!)
siteObject.setValue(itemNameTextField.text?.capitalizedString, forKey: "itemName")
siteObject.setValue(quantityNumber, forKey: "itemQnty")
fetchSite?.setValue(NSSet(object: siteObject), forKey: "reverseItems")
self.managedContext.saveOrLogError()
self.navigationController?.popViewControllerAnimated(true)
}
}
}
Once again if someone can point me in the right direction for saving my relationships to core data and retrieving these relationships would be great. Still new to core data so need all the help i can get!
Thank you
Your detail controller can have a property Site. In prepareForSegue you pass the selected Site object to the detail controller.
Both the site list and the list of items should be backed by NSFetchedResultsController, not in-memory arrays.