i used Core Date to save names and phone numbers
i would like to make a call by touching cell
here is my code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let people = try PersistenceService.context.fetch(fetchRequest)
self.people = people
self.tableView.reloadData()
} catch {}
}
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "Add Person", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addTextField { (textField) in
textField.placeholder = "Phone number"
textField.keyboardType = .numberPad
}
let action = UIAlertAction(title: "Post", style: .default) { (_) in
let name = alert.textFields!.first!.text!
let phoneNumber = alert.textFields!.last!.text!
let person = Person(context: PersistenceService.context)
person.name = name
person.phoneNumber = phoneNumber
PersistenceService.saveContext()
self.people.append(person)
self.tableView.reloadData()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override var prefersStatusBarHidden: Bool {
return true
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].phoneNumber
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
people.remove(at: indexPath.item)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UIApplication.shared.openURL(NSURL(string: "tel://" + (people[indexPath.row].phoneNumber?.description)!)! as URL)
print(people[indexPath.row].phoneNumber?.description)
}
}
No need to add an #IBAction, you can use didSelectRow from UITableViewDelegate
You are already implementing the didSelectRowAt use IBActions only inside UITableView if you have a UIButton inside the UITableViewCell
Related
I'm developing a todo list app that links up with a Realm database, however when I'm trying to use the 'editingStyle' method which allows users to swipe on the cell to delete the data from the UI & the Realm database the cell doesn't swipe, the app has 2 screens, this method works fine on one the first one but it does not work on the other screen, the cell works fine it just won't swipe.
My code:
import UIKit
import RealmSwift
class CategoryViewController: UITableViewController {
var categories: Results<Category>?
let realm = try! Realm()
override func viewDidLoad() {
super.viewDidLoad()
loadCategories()
tableView.rowHeight = 60.0
}
//MARK: - Creating the table view cell
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories Added Yet"
return cell
}
//MARK: - This will remove a category from the UI & the Realm database, this is a built in swift method
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let deleteAction = categories?[indexPath.row] {
do {
try realm.write({
realm.delete(deleteAction)
})
} catch {
print("Error deleting the cell \(error)")
}
}
}
tableView.deleteRows(at: [indexPath], with: .fade)
}
//MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! TodoListViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedCategory = categories?[indexPath.row]
}
}
//MARK: - Add New Categories
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add", style: .default) { (action) in
// What happens when user clicks add button
let newCategory = Category()
newCategory.name = textField.text!
self.saveCategories(category: newCategory)
}
alert.addAction(action)
alert.addTextField { (field) in
textField = field
textField.placeholder = "Add a new category"
}
present(alert, animated: true, completion: nil)
}
//MARK: - Data Manipulation Methods
func saveCategories(category: Category) {
do {
try realm.write({
realm.add(category)
})
} catch {
print("Error saving category \(error)")
}
tableView.reloadData()
}
func loadCategories() {
categories = realm.objects(Category.self)
tableView.reloadData()
}
}
Add this:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
Your code seems correct. I think there's something wrong with realm.delete(_:) method which possibly throws an error which executes the catch block instead of deleting the row. Try to see if there is something wrong within the try block with a few print statements. And if all fails, try making the following changes
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let deleteAction = categories?[indexPath.row] {
do {
try realm.write({
realm.delete(deleteAction)
})
// New snippet
tableView.deleteRows(at: indexPath, with: .fade)
self.tableView.reloadData()
} catch {
print("Error deleting the cell \(error)")
}
}
}
}
I want to make the transition from the UITableView cell to the view controller. My didSelectRowAt isn't working.
Here is code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var goals = [String]()
#IBAction func onAddTapped() {
let alert = UIAlertController(title: "Add Goal", message: nil, preferredStyle: .alert)
alert.addTextField { (dessertTF) in
dessertTF.placeholder = "Enter Goal"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
guard let goal = alert.textFields?.first?.text else { return }
print(goal)
self.add(goal)
}
alert.addAction(action)
present(alert, animated: true)
}
func add(_ goal: String) {
let index = 0
goals.insert(goal, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "segue", sender: self)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let dessert = goals[indexPath.row]
cell.textLabel?.text = goals
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
desserts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
The problem is that you aren't creating your cells correctly.
Inside of your cellForRow function change
let cell = UITableViewCell()
to this:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
Make sure that in your storyboard file, that you set the cell identifier to "Cell"
My code right now just lists things you manually enter. However when the user switches view controllers the code disappears. I tried to use userdefualts to save my current code in the number of rows in selection function but it does not save the items in the tableview cells. I just want to save whatever is in the tableview cells.
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items: [String] = [""]
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let userDefaults = UserDefaults.standard
userDefaults.setValue(items, forKey: "items")
userDefaults.synchronize()
return items.count
}
func alert(){
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
let add = UIAlertAction(title: "Add", style: .default){
(action) in
let textfield = alert.textFields![0]
self.items.append(textfield.text!)
self.listTableView.reloadData()
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}}
You have a number of issues in your code:
You never load your data back from UserDefaults. - This is the big one
There is no need to call synchronise.
You should save your data when data is added/deleted, not in numberOfRowsInSection.
It will look nicer if you insert a new row rather than reloading the whole table
I would suggest something like:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items = [String]()
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
self.items = UserDefaults.standard.stringArray(forKey:"items") ?? [String]()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func saveData() {
userDefaults.standard.set(items, forKey: "items")
}
func alert(){
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
let add = UIAlertAction(title: "Add", style: .default){
(action) in
guard let textfield = alert.textFields?.first else {
return
}
if let newText= textfield.text {
self.items.append(newText)
saveData()
let indexPath = IndexPath(row: items.count - 1, section: 0)
self.listTableView.insertRows(at: [indexPath], with: .automatic)
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
saveData()
}
}
Also, you shouldn't really use UserDefaults for data storage this way, but I presume this is just a simple learning exercise.
I'm making an app which uses the Blogger API. In the first tab, I can search for posts and display their contents. Also, I can add posts to the "Favorites" section in the second tab. All is working, until I close the app. After re-launching, the Favorites section is gone. I tried to implement UserDefaults so that the Favorites section does not become empty after killing the app, but it does not work.
This is the code for the button which adds the post to Favorites:
vc.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
func addTapped() {
offlineTitles.append(cellText)
titlesArray.append(cellText)
subtitlesArray.append(cellSubtitle)
let defaults = UserDefaults.standard
defaults.set(titlesArray, forKey: "title")
defaults.set(subtitlesArray, forKey: "subtitle")
NotificationCenter.default.post(name: .reload, object: nil)
let ac = UIAlertController(title: "Added!", message: "Post added to favorites", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Great!", style: .default))
present(ac, animated: true)
}
and this for the FavoritesViewController.swift :
import UIKit
var offlineTitles = [String]()
var titlesArray = [String]()
var subtitlesArray = [String]()
extension Notification.Name {
static let reload = Notification.Name("reload")
}
class OfflineViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reloadTableData(_:)), name: .reload, object: nil)
self.tableView.allowsMultipleSelectionDuringEditing = false
}
func reloadTableData(_ notification: Notification) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titlesArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OfflineCell", for: indexPath)
let defaults = UserDefaults.standard
let userDefaultsTitleArray = defaults.array(forKey: "title") as? [String] ?? [String]()
let userDefaultsSubtitleArray = defaults.array(forKey: "subtitle") as? [String] ?? [String]()
let title = userDefaultsTitleArray[indexPath.row]
let subtitle = userDefaultsSubtitleArray[indexPath.row]
cell.textLabel?.text = title
cell.detailTextLabel?.text = subtitle
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = FavouritesViewController()
navigationController?.pushViewController(vc, animated: true)
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
offlineTitles.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
It appears that you're reading user defaults in cellForRowAt. This is not only inefficient (if you had five favorites, you'd be reading it in five times), but is at the wrong time. For example, what will numberOfRowsInSection return? By the time that's called, you haven't yet read the user defaults into your arrays.
You should read user defaults into your arrays in viewDidLoad (as well as possibly in your reloadTableData, too).
Userdefaults-saved data is passed from my TextViewCotroller to TextViewTableController successfully, but not perfectly successful. This is because when my TextView, which has some data already, is re-saved, it causes a duplicate.
For example, if the firstly saved data is like "hello, I like bagels" and if I edit it to like "hello, I like bagels and chololate cookies" and re-save it,
At the 0 index of my TableView is "hello, I like bagels and chololate cookies"
At the 1 index of my TableView is "hello, I like bagels"
When this is repeatedly done, there are multiple duplicates of the same text in my TableView. This is so annoying that I really want to detect the cause of this issue. However, I have no idea of fixing this bug.
TextTableViewController:
class TextTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
self.tableView.reloadData()
}
func saveTextData() -> [String] {
if let textData = userTextDataSave.array(forKey: "txtData") as? [String] {
return textData
}
return []
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return saveTextData().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellForText", for: indexPath)
cell.backgroundColor = UIColor.clear
cell.preservesSuperviewLayoutMargins = false
cell.textLabel?.text = saveTextData()[indexPath.row]
cell.textLabel?.font = UIFont.systemFont(ofSize: 20)
tableView.reloadData()
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
removeHistory(index: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "text",sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
if (segue.identifier == "text") {
let subVC: TextViewController = (segue.destination as? TextViewController)!
let indexPath = tableView.indexPathForSelectedRow;
subVC.textFromCell = saveTextData()[(indexPath?.row)!]
}
}
}
TextViewController and functions for saving text data:
func save(){
let alert = UIAlertController(title: "titile", message: "save?", preferredStyle: .alert)
let noAction = UIAlertAction(title: "Cancel", style: .default, handler: { Void in
})
let okAction = UIAlertAction(title: "Save", style: .default, handler: { Void in
self.addTextData(text: self.myTextView.text)
})
alert.addAction(noAction)
alert.addAction(okAction)
present(alert, animated: false, completion: nil)
}
func saveTextData() -> [String] {
if let textData = userTextDataSave.array(forKey: "txtData") as? [String] {
return textData
}
return []
}
func addTextData(text: String) {
var data = saveTextData()
for d in data {
if d == "" {
return
}
}
data.insert(text, at: 0)
userTextDataSave.set(text, forKey: "txtData")
userTextDataSave.synchronize()
}
Try this :
func addTextData(text: String) {
var data = saveTextData()
for d in data {
if d == "" {
return
}
if d == text {
return
}
}
userTextDataSave.set(text, forKey: text)
userTextDataSave.synchronize()
}