iOS Swift 3: does retain cycle happens in this case? - ios

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CheckoutCell") as! CheckoutCell
let product = shoppingCart[indexPath.row]
var tfQuantity : UITextField!
cell.clickEditAction = { [weak self] celll in
guard let ss = self else { return }
let alert = UIAlertController(title: nil, message: "Enter new quantity", preferredStyle: .alert)
alert.addTextField { (textfield) in
tfQuantity = textfield
}
let okAction = UIAlertAction(title: "OK", style: .default) { (action) in
if tfQuantity.text == ""{
return
}
if let newQuantity = Int(tfQuantity.text){
product.quantity = newQuantity
self.tbvCheckout.reloadData()
}
return
}
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
return cell
}
This line of code:
self.tbvCheckout.reloadData()
If I don't use [weak self] or [unowned self], does it create retain cycle between current object & UIAlertAction instance?
What if I use this code instead: tableView.reloadData()?

Couple of things:
First, You have created a weak reference, but I don't see you using it in the code.
guard let ss = self else { return }
Any reference to self should be via this weak self variable "ss" that you have created.
Second, The alert action block should also have weak reference to self
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] (action) in
if tfQuantity.text == ""{
return
}
if let newQuantity = Int(tfQuantity.text){
product.quantity = newQuantity
self?.tbvCheckout.reloadData()
}
return
}

Related

How to edit row in tableView and save changes in the coreData?

I created a simple toDoList with a simple coreData. Added creating a row, deleting a row, but can't change the row.
I can't understand how to do it.
I added alertСontroller to change it.
My coreData consists of one property: name.
And I make an interface without a storyboard, just code.
How to update a CoreData object?
import UIKit
import CoreData
class ViewController: UITableViewController {
private let cellID = "cell"
private var tasks = [Task]()
private let appDelegate = UIApplication.shared.delegate as! AppDelegate //
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "To do list"
navigationController?.navigationBar.barTintColor = UIColor(displayP3Red: 21/255,
green: 101/255,
blue: 192/255,
alpha: 1)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add",
style: .plain,
target: self,
action: #selector(addNewTask))
navigationController?.navigationBar.tintColor = .white
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
do {
tasks = try managedContext.fetch(fetchRequest)
} catch let error {
print("Failed to fetch data", error)
}
}
// MARK: Table View Data Source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
let task = tasks[indexPath.row]
cell.textLabel?.text = task.name
return cell
}
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editNotesAction = UITableViewRowAction(style: .default, title: "Edit") { (action: UITableViewRowAction, indexPath: IndexPath) in
let alert = UIAlertController(title: "Edit", message: "", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default, handler: { (action) in
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
self.present(alert, animated: true)
}
editNotesAction.backgroundColor = .blue
let deleteNotesAction = UITableViewRowAction(style: .default, title: "Delete") { (action: UITableViewRowAction, indexPath: IndexPath) in
let managedContext = self.appDelegate.persistentContainer.viewContext
managedContext.delete(self.tasks[indexPath.row])
self.tasks.remove(at: indexPath.row)
do {
try managedContext.save()
} catch let error {
print(error.localizedDescription)
}
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
return [deleteNotesAction, editNotesAction]
}
#objc private func addNewTask() {
let alert = UIAlertController(title: "New Task", message: "", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { _ in
guard let task = alert.textFields?.first?.text, task.isEmpty == false else { return }
self.saveData(task)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
private func saveData(_ taskName: String) {
let managedContext = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Task", in: managedContext) else { return }
let task = NSManagedObject(entity: entity, insertInto: managedContext) as! Task
task.name = taskName
do {
try managedContext.save()
tasks.append(task)
} catch let error {
print("Failed to save task", error.localizedDescription)
}
}
}
Your save action needs to take the value out of the text field and assign it to the right managed object.
let saveAction = UIAlertAction(title: "Save", style: .default, handler: { (action) in
guard let textField = alert.textFields.first else { return }
let task = tasks[indexPath.row]
task.name = textField.text
})
If name is not optional you will need to use nil-coalescing to supply a default value for the name in case the text field is empty. (task.name = textField.text ?? "SomeDefaultName")

Swipe to delete with multiple options

When the user swipes a table view cell in the chat view controller I would like to offer the option to either Block and delete that user, or to only delete the chat from the user. Is there a way I can have the swipe to delete option to have both options available? Should I be adding the block user option on a different page or will there be a way to have both in the commit editingStyle function.
class Conversation {
var key:String
var sender:String
var recipient:String
var date:Date
var recentMessage:String
var seen:Bool
init(key:String, sender: String, recipient:String, date:Date, recentMessage:String, seen:Bool) {
self.key = key
self.sender = sender
self.recipient = recipient
self.date = date
self.recentMessage = recentMessage
self.seen = seen
}
// Returns the UID of the conversations partner
// i.e NOT the UID of the current user
var partner_uid:String {
guard let uid = Auth.auth().currentUser?.uid else { return "" }
if sender != uid {
return sender
}
return recipient
}
func printAll() {
print("key: \(key)")
print("sender: \(sender)")
print("recentMessage: \(recentMessage)")
}
}
class ChatsTableViewController:UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
var conversations = [Conversation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds)
let nib = UINib(nibName: "ChatTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "chatCell")
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
title = "CHAT"
view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let cell = tableView.cellForRow(at: indexPath) as! ChatTableViewCell
let name = cell.usernameLabel.text!
let actionSheet = UIAlertController(title: "Block conversation with \(name)?", message: "Further messages from \(name) will be muted.", preferredStyle: .alert)
let cancelActionButton: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in }
actionSheet.addAction(cancelActionButton)
let deleteActionButton: UIAlertAction = UIAlertAction(title: "Block", style: .destructive)
{ action -> Void in
self.muteConversation(self.conversations[indexPath.row])
}
let deleteOnlyButton: UIAlertAction = UIAlertAction(title: "Only Delete", style: .destructive)
{ action -> Void in
print("only delete selected ")
}
actionSheet.addAction(deleteActionButton)
actionSheet.addAction(deleteOnlyButton)
self.present(actionSheet, animated: true, completion: nil)
}
}
func muteConversation(_ conversation:Conversation) {
guard let user = Auth.auth().currentUser else { return }
let ref = Database.database().reference()
let obj = [
"social/blocked/\(user.uid)/\(conversation.partner_uid)" : true,
"social/blockedBy/\(conversation.partner_uid)/\(user.uid)" : true,
"conversations/users/\(user.uid)/\(conversation.partner_uid)/muted": true
] as [String:Any]
print("OBBJ: \(obj)")
ref.updateChildValues(obj, withCompletionBlock: { error, ref in
if error != nil {
let alert = UIAlertController(title: "Error deleting conversation!", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
} else {
let alert = UIAlertController(title: "Conversation blocked!", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
}
})
}
}
Try this code and replace Action1 & Action2 with your preferred actions.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let action1 = UITableViewRowAction(style: .default, title: "Action1", handler: {
(action, indexPath) in
print("Action1")
})
action1.backgroundColor = UIColor.lightGray
let action2 = UITableViewRowAction(style: .default, title: "Action2", handler: {
(action, indexPath) in
print("Action2")
})
return [action1, action2]
}
Download/Refer https://github.com/CEWendel/SWTableViewCell/archive/master.zip, integrate this third party library to your project and try the below code in your ViewController
Step 1:
add the delegate SWTableViewCellDelegate to your ViewController
Step 2:
in your cellForRow
cell.leftUtilityButtons = leftButtons() as [AnyObject]
cell.rightUtilityButtons = self.rightButtons() as [AnyObject]
cell.delegate = self;
Step 3:
customise your left/right side buttons on swipes
func leftButtons() -> NSMutableArray
{
let leftUtilityButtons : NSMutableArray = NSMutableArray()
leftUtilityButtons.sw_addUtilityButton(with: UIColor.orange, title: "Block")
leftUtilityButtons.sw_addUtilityButton(with: UIColor.green, title: "Remove User")
return leftUtilityButtons
}
func rightButtons() -> NSMutableArray {
let leftUtilityButtons : NSMutableArray = NSMutableArray()
leftUtilityButtons.sw_addUtilityButton(with: UIColor.red, title: "Delete Chat")
return leftUtilityButtons
}
Step 4:
handle actions with these two delegate methods
// click event on left utility button
func swipeableTableViewCell(_ cell: SWTableViewCell, didTriggerLeftUtilityButtonWith index: Int)
{
switch index
{
case 0:
// Handle your button1 action (Block User)
break
case 1: break
// Handle your button2 action (Remove User)
default:
break
}
}
// click event on right utility button
func swipeableTableViewCell(_ cell: SWTableViewCell, didTriggerRightUtilityButtonWith index: Int)
{
//handle your right button action (Delete Chat)
}
Thats it...!
You can modify your function like this to work
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let blockAction = UITableViewRowAction(style: .normal, title: "Block") { (rowAction, indexPath) in
self.muteConversation(self.conversations[indexPath.row])
}
let deleteAction = UITableViewRowAction(style: .destructive, title: "Only Delete") { (rowAction, indexPath) in
print("only delete selected ")
}
blockAction.backgroundColor = UIColor.gray
return [blockAction, deleteAction]
}
and if you want to show action sheet instead you can use this code
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let moreAction = UITableViewRowAction(style: .normal, title: "More") { (rowAction, indexPath) in
self.showActionSheet(indexPath)
}
moreAction.backgroundColor = UIColor.blue
return [moreAction]
}
func showActionSheet(_ indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ChatTableViewCell
let name = cell.usernameLabel.text!
let actionSheet = UIAlertController(title: "Block conversation with \(name)?", message: "Further messages from \(name) will be muted.", preferredStyle: .alert)
let cancelActionButton: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in }
actionSheet.addAction(cancelActionButton)
let deleteActionButton: UIAlertAction = UIAlertAction(title: "Block", style: .destructive)
{ action -> Void in
self.muteConversation(self.conversations[indexPath.row])
}
let deleteOnlyButton: UIAlertAction = UIAlertAction(title: "Only Delete", style: .destructive)
{ action -> Void in
print("only delete selected ")
}
actionSheet.addAction(deleteActionButton)
actionSheet.addAction(deleteOnlyButton)
self.present(actionSheet, animated: true, completion: nil)
}

UICollectionView does not refresh in swift 3

I add UICollectionView in my app and each cell has "Delete" trash button.
When I click on this button, it will show alert message to ask. After clicks on OK button, the item in Collection View will delete. However, the UI does not refresh , the item was not deleted.
My Code is ...
override func viewDidLoad() {
super.viewDidLoad()
myCollectionView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
debugPrint("viewDidAppear")
callWatchLater()
}
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async(execute: {
debugPrint("Appear")
self.myCollectionView.reloadData()
})
}
override func viewDidLayoutSubviews() {
DispatchQueue.main.async(execute: {
debugPrint("SubViews")
self.myCollectionView.reloadData()
})
}
Code for Alert Controller is
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "watchCell", for: indexPath) as! WatchLaterTableCell
let watchTableList = self.watchLaterTable[indexPath.row]
cell.deleteMovie.addTarget(self, action:#selector(showAlert(sender:)), for: .touchUpInside)
}
return cell
}
func showAlert(sender:UIButton!)
{
debugPrint("Press Button")
let alert = UIAlertController(title: "Are you really want to delete this movie?", message: "", preferredStyle: UIAlertControllerStyle.alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
self.callRest()
debugPrint("Press OK")
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) {
UIAlertAction in
_ = self.navigationController?.popViewController(animated: true)
}
// Add the actions
alert.addAction(okAction)
alert.addAction(cancelAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alert, animated: true, completion: nil)
})
}
Code For callRest() is
func callRest(){
SwiftLoading().showLoading()
if Reachability().isInternetAvailable() == true {
rest.auth(auth: access_token)
rest.delete(StringResource().mainURL + "user/" + user_id + "/watch/later/" + String(vedioId) , parma: [:], finished: {(result: NSDictionary, status : Int) -> Void in
debugPrint(result)
if(status == 200){
let data = result["data"] as! NSString
self.messages = String(describing: data)
DispatchQueue.main.sync{[unowned self] in
let alertController = UIAlertController(title: "", message: self.messages , preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
debugPrint("call rest Press OK")
self.callWatchLaterGetAPI()
}
alertController.addAction(okAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alertController, animated: true, completion: nil)
})
}
self.myCollectionView.reloadData()
SwiftLoading().hideLoading()
}else{
let error = result["error"] as! NSArray
for item in 0...(error.count) - 1 {
let device : AnyObject = error[item] as AnyObject
self.messages = device["message"] as! String
DispatchQueue.main.sync{[unowned self] in
let alertController = UIAlertController(title: "", message: self.messages , preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
debugPrint("Press OK")
}
alertController.addAction(okAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alertController, animated: true, completion: nil)
})
}
SwiftLoading().hideLoading()
}
}
})
}else{
noInternetConnection()
}
}
However, the CollectionView does not refresh . Can anyone help me this, please?

Subscribing to UIButton tap in a UICollectionViewCell in RxSwift?

I'm new to RxSwift, trying to wrap my head around it. I was having trouble getting a UIButton in a cell to show a UIAlertController when it's pressed.
private func setupCellConfiguration() {
bookListViewModel.data
.bindTo(collectionView.rx.items(cellIdentifier: BookListCell.Identifier, cellType: BookListCell.self)) { [unowned self] (row, element, cell) in
cell.configureForBook(book: element)
cell.moreButton.rx.tap.subscribe { [weak self] in
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {(action) in
self?.dismiss(animated: true, completion: nil)
}
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in
}
alertController.addAction(destroyAction)
self?.present(alertController, animated: true)
}
.addDisposableTo(self.disposeBag)
}
.addDisposableTo(disposeBag)
}
Nothing happens when it's pressed. What am I doing wrong here?
I actually prefer to assign cell button action on its subclass. The problem is I think every cell should have it's own disposeBag and it should reinitialize every time it is reused.
Example: Haven't tested on code, if there's any problem let me know
private func setupCellConfiguration() {
bookListViewModel.data
.bindTo(collectionView.rx.items(cellIdentifier: BookListCell.Identifier, cellType: BookListCell.self)) { [unowned self] (row, element, cell) in
cell.delegate = self
cell.configureForBook(book: element)
}
.addDisposableTo(disposeBag)
}
// Your Cell Class
var disposeBag = DisposeBag()
var delegate: UIViewController?
func configureForBook(book: Book) {
self.moreButton.rx.tap.subscribe { [unowned self] in
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {(action) in
self?.dismiss(animated: true, completion: nil)
}
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in
}
alertController.addAction(destroyAction)
self.delegate?.present(alertController, animated: true)
}
.addDisposableTo(self.disposeBag)
}
override func prepareForReuse() {
disposeBag = DisposeBag()
}

UITableView will not refresh once list is deleted. self.tableView.reloadData() does not work either

I'm relatively new to developing Swift and iOS, but not new to code. I'm a little annoyed with the table view.
I have tried countless times to use self.tableView.reloadData() but to no avail. I have also tried
async call dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
But this doesn't want to work either. I'll post function so that maybe someone can enlighten me where I goofed up. I really do appreciate the help.
#IBAction func clearList(sender: AnyObject) {
let alert = UIAlertController(title: "Clear List?",
message: "Are you sure you want to clear the list?",
preferredStyle: .Alert)
let yesClearAction = UIAlertAction(title: "Yes", style: .Default, handler: { (action:UIAlertAction) -> Void in
self.clearListNow("GroceryList")
})
let noClearAction = UIAlertAction(title: "No", style: .Default, handler: { (action:UIAlertAction) -> Void in
//do nothing aka don't clear list
})
alert.addAction(yesClearAction)
alert.addAction(noClearAction)
presentViewController(alert, animated: true, completion: nil)
self.tableView.reloadData()
}
And just in case anyone wanted to see what clearListNow does...
func clearListNow(entity: String) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "GroceryList")
fetchRequest.returnsObjectsAsFaults = false
do
{
let results = try managedContext.executeFetchRequest(fetchRequest)
for managedObject in results
{
let managedObjectData:NSManagedObject = managedObject as! NSManagedObject
managedContext.deleteObject(managedObjectData)
}
} catch let error as NSError {
print("Detele all data in \(entity) error : \(error) \(error.userInfo)")
}
}
I have also tried to use self.tableView.reloadData() in the clearListNow function. I have tried to place the statement in both at the same time, and separately and neither scenario worked.
Reload your tableView when use press Yes in your alert as shown into below code:
let yesClearAction = UIAlertAction(title: "Yes", style: .Default, handler: { (action:UIAlertAction) -> Void in
self.clearListNow("GroceryList")
self.tableView.reloadData()
})
try to reload data in yesClearAction
you should have something like :
#IBAction func clearList(sender: AnyObject) {
let alert = UIAlertController(title: "Clear List?",
message: "Are you sure you want to clear the list?",
preferredStyle: .Alert)
let yesClearAction = UIAlertAction(title: "Yes", style: .Default, handler: {(action:UIAlertAction) -> Void in
self.clearListNow("GroceryList")
self.tableView.reloadData()
})
let noClearAction = UIAlertAction(title: "No", style: .Default, handler: { (action:UIAlertAction) -> Void in
//do nothing aka don't clear list
})
alert.addAction(yesClearAction)
alert.addAction(noClearAction)
presentViewController(alert, animated: true, completion: nil)
}
Let me know if this works ! :)
UPDATE
I have done this code and it works fine for me
import UIKit
class ViewController: UIViewController ,UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
var rows: NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
self.rows = ["1","2","3","4","5","6","7"]
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func clearList(sender: AnyObject) {
let alert = UIAlertController(title: "Clear List?",
message: "Are you sure you want to clear the list?",
preferredStyle: .Alert)
let yesClearAction = UIAlertAction(title: "Yes", style: .Default, handler: { (action:UIAlertAction) -> Void in
self.clearListNow("GroceryList")
self.tableView.reloadData()
})
let noClearAction = UIAlertAction(title: "No", style: .Default, handler: { (action:UIAlertAction) -> Void in
//do nothing aka don't clear list
})
alert.addAction(yesClearAction)
alert.addAction(noClearAction)
presentViewController(alert, animated: true, completion: nil)
}
func clearListNow(entity: String) {
self.rows.removeAllObjects()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.rows.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("FruitCell", forIndexPath: indexPath) as? MyCell
cell!.textLabel?.text = self.rows[indexPath.row] as? String
return cell!
}
}
here a screen shot of my storyboard :
As per your current code TableView Reload after AlertView present
so, tableView DataSource (Array) not change and tableview Display datasource(array) data.
try to relod table after
self.clearListNow("GroceryList")
i.e.
self.clearListNow("GroceryList")
self.tableView.reloadData()
or
into func clearListNow(entity: String)
after for loop
i.e.
for managedObject in results
{
let managedObjectData:NSManagedObject = managedObject as! NSManagedObject
managedContext.deleteObject(managedObjectData)
}
self.tableView.reloadData()
so tableview get latest datasource and display it.
Let me know if this works...

Resources