There are a lot of question with the same title but somehow I felt that every case is different as my problem is still not resolved and I've no clue why and also there are not enough solutions for Swift. So here is my scenerio:
I've used CarbonKit to show 4 view controllers in a single view controller. The FourthViewController sends a get call using AlamoFire. It successfully loads the data and reload tableView. However when I drag this table view my app crashes and I get EXC_BREAKPOINT and sometimes EXC_BAD_ACCESS for the same thing.
Here is what I've tried:
Upon searching I came to know about zombies. So I enabled it and when the app gives me EXC I get this:
-[CALayer removeAllAnimations]: message sent to deallocated instance
So as I checked there was no animation I might have been using in this class. But considering it's inside CarbonKit they might have something to do with it but I'm unable to figure it out.
I run Product > Analyze from xCode and the most of the blue icons are on FBSDK which have nothing to do with this class so it was not useful to debug at all.
Here are the screenshots:
and
and here is what Instruments 9.0 zombies message gave me:
and
I get this in console of xCode:
If you need more information I'll update my questions.
UPDATE
Here is my code when I'm getting the data from web using Alamofire. Now this call runs successfully and I can scroll down to view all the data that is loaded in the table. But when i scroll up then it crashes with the above errors.
let headers = [
"security-token": "MY_SECURITY_CODE_HERE",
"Content-Type": "application/x-www-form-urlencoded"
]
let url = "URL_GOES_HERE"
//print(url)
Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
if let result = response.result.value as? [String: AnyObject] {
switch response.result {
case .success:
if result["status"] as! Int == 0
{
self.clearAllNotice()
let alert = UIAlertController(title: "Error",
message: "\(result["msg"] ?? "Our Server is not responding at the moment. Try again later!" as AnyObject)",
preferredStyle: UIAlertControllerStyle.alert)
let cancelAction = UIAlertAction(title: "OK",
style: .cancel, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true)
}
else
{
self.clearAllNotice()
for docReviewDict in (result["data"] as! [AnyObject]) {
let docReview = DoctorReview(dict: docReviewDict as! [String : AnyObject])
self.mainDoctorReviewsArray.append(docReview)
}
self.tableView.reloadData()
}
case .failure(let error):
self.clearAllNotice()
let alert = UIAlertController(title: "Something is wrong",
message: error.localizedDescription,
preferredStyle: UIAlertControllerStyle.alert)
let cancelAction = UIAlertAction(title: "OK",
style: .cancel, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true)
}
} else {
print("Somethins is wrong with the URL")
}
}
Just in case here is the cell which is loaded in the tableview.
Latest:
Based on the comment I've done this:
DispatchQueue.main.async {
self.tableView.reloadData()
}
but still the same result I'am getting
Thread 1: EXC_BAD_ACCESS (code=1, address=0xb4c4beb8)
on let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCell") as! ReviewsTableViewCell line when I drag down on table view.
IMPLEMENTATION OF CELL
The class name is ReviewsViewController which has a #IBOutlet fileprivate var tableView: UITableView!.
Then in viewDidLoad():
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
Then I've table view delegate and datasource:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainDoctorReviewsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCell") as! ReviewsTableViewCell //WHEN THE APP IS CRASHED AS I DRAG THE TABLE VIEW IT CRASH ON THIS LINE. As this line is highlighted in red with message "Thread 1: EXC_BREAKPOINT (code=1, subcode=0x186ddd61c)"
cell.userPhoto.layer.cornerRadius = cell.userPhoto.frame.width / 2;
cell.userPhoto.layer.masksToBounds = true
cell.userName.text = mainDoctorReviewsArray[indexPath.row].name
cell.starRating.settings.fillMode = .precise
if let rate = mainDoctorReviewsArray[indexPath.row].rating
{
cell.starRating.rating = Double(rate)!
}
else {
cell.starRating.rating = 0
}
cell.desc.text = mainDoctorReviewsArray[indexPath.row].feedback
cell.dateOfReview.text = mainDoctorReviewsArray[indexPath.row].dates
cell.timeOfReview.text = mainDoctorReviewsArray[indexPath.row].times
return cell
}
and in the cell class there is just:
import UIKit
import Cosmos
class ReviewsTableViewCell: UITableViewCell {
#IBOutlet var timeOfReview: UILabel!
#IBOutlet var dateOfReview: UILabel!
#IBOutlet var desc: UITextView!
#IBOutlet var starRating: CosmosView!
#IBOutlet var userName: UILabel!
#IBOutlet var userPhoto: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The screenshot of tableView is above in this question.
This is the parent class under which 4 ViewControllers loads: dropbox.com/s/d5sh4ej1ssv6873/… And this is the class which has the tableview is and on scroll it crashed the app: dropbox.com/s/3kiju9hn3uewzar/ReviewsViewController.swift?dl=0
As someone mentioned above it probably is related with working with ui objects not from main thread.
If your using xCode 9 you should see as a runtime warning. (You can set checkmark in EditScheme->Diagnostics->MainThreadChecker->pause on issues).
Also you can override dealloc method of you ReviewsTableViewCell and add some logs\asserts to check if it's called from main thread.
Usually it happens if you pass your view\cell to some block, and you will have this crash on releasing this object when exiting from block.
I'm gonna make some small suggestions to clean up your code. Not sure if it will solve the problem but it will help to ensure that your issue isn't based on something trivial.
I'm not sure where you're calling it, but in the request load completion handler make sure to capture self weakly.
Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON { [weak self] response in
This way you're not forcing the object to stay alive until the loading finishes, which may cause it to load in the wrong place.
Make sure that the completion block (mentioned above) is running on the main thread. You're making calls to UI, so just check it to be safe.
Your IB outlets should be declared as #IBOutlet weak var foo: UIView? You might be causing yourself problems by strongly referencing the views.
dequeueReusableCell(withReuseIdentifier:) can return nil. You should be using dequeueReusableCell(withReuseIdentifier:for:) - and don't forget to register the cell using register(_:forCellWithReuseIdentifier:) first.
Did you register the cell class type?
If not add in viewDidLoad:
self.tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "ReviewCell")
Next just dequeue it in cellForRow:
let cell = tableView.dequeueReusableCellFor(identifier: "ReviewCell", indexPath: indexPath) as! ReviewsTableViewCell
Check if the error is still happening
In my case, this happened when I executed this (myView is a subview on a cell)
myView.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
I don't find anything removing a layer from your code but if you did it outside of that, then I think that's the problem.
Related
I'm currently working on an app that has a table-view-like collection view and some other view controllers. Basically, my question is how can I update the indexPath of each cell when one of the collection view cells is deleted.
I attached my view controller file below, but here is what's going on on the app.
When a user opens the table-view-like collection view (in EventCollectionVC), it reloads the data from a database and presents them on the collection view. I also added the code to the navigation bar button item that the user can change the collection view to the edit mode. While in the edit mode, a small ellipsis.circle (SF symbols) is displayed on the collection view cell. When a user taps the ellipsis.circle icon, it displays a new view controller (ModalVC) and lets the user select either delete or edit the cell. When the user selects delete, it shows an alert to delete the cell and delete the cell information with modal dismiss (which means the ModalVC is closed and the MyCollectionVC is displayed now).
Since I have to make the two view controllers (like getting cell information from EventCollectionVC and present in ModalVC) talk to each other, I need to use the indexPath.row to get the information of the cell. Before deleting the cells, the numbers of indexPath.row in the collection view is like
[0,1,2,3,4,5]
But, for example, after I delete the second (indexPath.row = 1) cell and when I try to delete another item, the indexPath becomes
[0,2,3,4,5]
and I can see the collection view's index is not refreshed.
So my question is how can I update/refresh the cell's indexPath.row value after I delete a cell from the collection view?
This is the code with some explanations.
import UIKit
class EvnetCollectionViewController: UIViewController {
var EventDataSource: EventDataSource! // <- this is a class for the Model, and it stores array or Events
let ListView = ListView() // view file
var collectionViewDataSource: UICollectionViewDiffableDataSource<Section, Event>?
var targetEventIndex = Int() // variable to store the index of the event when event cell is tapped
override func loadView() {
view = ListView
}
override func viewDidLoad() {
super.viewDidLoad()
configureNavItem()
setupCollectionView()
displayEvents()
}
func configureNavItem() {
navigationItem.rightBarButtonItem = editButtonItem
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if (editing){
ListView.collectionView.isEditing = true
} else {
ListView.collectionView.isEditing = false
}
}
func setupCollectionView() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Event> { cell, indexPath, Event in
var content = UIListContentConfiguration.cell()
content.text = Event.title
cell.contentConfiguration = content
let moreAction = UIAction(image: UIImage(systemName: "ellipsis.circle"),
handler: { _ in
let vc = EventActionModalViewController(); // when the user select the cell in edit mode, it displays action modal VC and then come back to this VC with dismiss later
vc.modalPresentationStyle = .overCurrentContext
self.targetEventIndex = indexPath.row // I need targetEvemtIndex when user selects delete event in EventActionModalVC, so just sotre value in here
})
let moreActionButton = UIButton(primaryAction: moreAction)
let moreActionAccessory = UICellAccessory.CustomViewConfiguration(
customView: moreActionButton,
placement: .trailing(displayed: .whenEditing, at: { _ in return 0 })
)
cell.accessories = [
.disclosureIndicator(displayed: .whenNotEditing),
.customView(configuration: moreActionAccessory)
]
}
collectionViewDataSource = UICollectionViewDiffableDataSource<Section, Event>(collectionView: ListView.collectionView) {
collectionView, indexPath, Event in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: Event)
}
}
func displayEvents() {
EventDataSource = EventDataSource()
EventDataSource.loadData() // get Events in db and sore in an array Events
populate(with: EventDataSource.Events)
}
func populate(with Events: [Event]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Event>()
snapshot.appendSections([.List])
snapshot.appendItems(Events)
collectionViewDataSource?.apply(snapshot)
}
func showDeleteAlert() {
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in
self.EventDataSource.delete(at: targetEventIndex)
self.refreshList()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
self.showAlert(title: "Delete", message: nil, actions: [deleteAction, cancelAction], style: .actionSheet, completion: nil)
}
func refreshList() {
EventDataSource.loadData()
setupCollectionView() // since I write this code, it updates all of the indexPath in the collection view, but after deleting one item, the whole collection view is deleted and new collection view is reappeared.
populate(with: EventDataSource.Events)
}
}
I kinda know why this is happening. I only configure cell (in let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Event>...) once, so it won't update the cell information as well as its index path until I configure it again. But if I call setupCollectionView every after deleting one item, the whole collection view disappears and shows up again. Is it possible to reload the collection view list and updates its information without reloading the entire collection view?
Without writing setupCollectionView() in refreshList, the cell's indexPath is not refreshed and I get an error after I delete one cell and try to delete another one. So, I was wondering if there is a way to avoid recreating the whole collection view but update cells' indexPath when the user delete one of the cell in collection view.
I fixed the code in the refresh list function.
func refreshList() {
self.EventDataSource.loadData()
self.populate(with: self.EventDataSource.Events)
ListView.collectionView.reloadData()
}
I just needed to call reloadData after I populate all the data...
For my CollectionView I have this animation inside willDisplay :
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Add animations here
let animation = AnimationFactory.makeMoveUpWithFade(rowHeight: cell.frame.height, duration: 0.5, delayFactor: 0.1)
let animator = Animator(animation: animation)
animator.animate(cell: cell, at: indexPath, in: collectionView)
}
This is how the animation works (I implemented it for CollectionView) if you need it for more info.
Probelm:
Inside my project the user can create and delete an item.
Right now the collectionView is not animating after deleting even though I am calling reloadData:
extension MainViewController: DismissWishlistDelegate {
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption]) {
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
}
This is where I call the delegate inside my other ViewController:
func deleteTapped(){
let alertcontroller = UIAlertController(title: "Wishlist löschen", message: "Sicher, dass du diese Wishlist löschen möchtest?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Löschen", style: .default) { (alert) in
DataHandler.deleteWishlist(self.wishList.index)
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// change heroID so wishlist image doesnt animate
self.wishlistImage.heroID = "delete"
self.dismiss(animated: true, completion: nil)
// update datasource array in MainVC
self.dismissWishlistDelegate?.dismissWishlistVC(dataArray: self.dataSourceArray, dropDownArray: self.dropOptions)
}
let cancelAction = UIAlertAction(title: "Abbrechen", style: .default) { (alert) in
print("abbrechen")
}
alertcontroller.addAction(cancelAction)
alertcontroller.addAction(deleteAction)
self.present(alertcontroller, animated: true)
}
When creating the animation works just fine. This is how my createDelegateFunction looks like this:
func createListTappedDelegate(listImage: UIImage, listImageIndex: Int, listName: String) {
// append created list to data source array
var textColor = UIColor.white
if Constants.Wishlist.darkTextColorIndexes.contains(listImageIndex) {
textColor = UIColor.darkGray
}
let newIndex = self.dataSourceArray.last!.index + 1
self.dataSourceArray.append(Wishlist(name: listName, image: listImage, wishData: [Wish](), color: Constants.Wishlist.customColors[listImageIndex], textColor: textColor, index: newIndex))
// append created list to drop down options
self.dropOptions.append(DropDownOption(name: listName, image: listImage))
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: {
(result) in
// scroll to make newly added row visible (if needed)
let i = self.theCollectionView.numberOfItems(inSection: 0) - 1
let idx = IndexPath(item: i, section: 0)
self.theCollectionView.scrollToItem(at: idx, at: .bottom, animated: true)
})
}
Animation of IUCollectionView elements insertion and deletion is correctly done using finalLayoutAttributesForDisappearingItem(at:) and initialLayoutAttributesForAppearingItem(at:)
Little excerpt from Apple's documentation on finalLayoutAttributesForDisappearingItem(at:)
This method is called after the prepare(forCollectionViewUpdates:) method and before the finalizeCollectionViewUpdates() method for any items that are about to be deleted. Your implementation should return the layout information that describes the final position and state of the item. The collection view uses this information as the end point for any animations. (The starting point of the animation is the item’s current location.) If you return nil, the layout object uses the same attributes for both the start and end points of the animation.
I solved the problem by removing performBatchUpdates ... I have no idea why it works now and what exactly performBatchUpdates does but it works so if anyone wants to explain it to me, feel free :D
The final function looks like this:
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption], shouldDeleteWithAnimation: Bool, indexToDelete: Int) {
if shouldDeleteWithAnimation {
self.shouldAnimateCells = true
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// reload the collection view
theCollectionView.reloadData()
} else {
self.shouldAnimateCells = false
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
}
I have a UITable View in my program with dequeueReusableCells
I should load several images from server and show them in slide show
I have a custom cell and in configuring each cell I download the images in DispatchQueue.global(qos: .userInitiated).async and in DispatchQueue.main.async I add the downloaded pic to the slide show images
but when I start scrolling some of the cells that shouldn't have any pictures , have the repeated pics of another cell
Do you have any idea what has caused this ?!
I'm using swift and also ImageSlideShow pod for the slide show in each cell
Here is some parts of my code :
In my news cell class I have below part for getting images:
class NewsCell: UITableViewCell{
#IBOutlet weak var Images: ImageSlideshow!
#IBOutlet weak var SubjectLbl: UILabel!
#IBOutlet weak var NewsBodyLbl: UILabel!
func configureCell(news: OneNews) {
self.SubjectLbl.text = news.Subject
self.NewsBodyLbl.text = news.Content
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
for imgId in news.ImagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
if let d = data {
let img = UIImage(data: data!)!
imageSrc.append(ImageSource(image: img))
self.Images.setImageInputs(imageSrc);
}
}
}
}
}
self.Images.slideshowInterval = 3
}
And this is cellForRow method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = generalNewsTableView.dequeueReusableCell(withIdentifier: "NewsCell" , for: indexPath) as! NewsCell
if let news = NewsInfo.sharedInstance.getGeneralNews(){
cell.configureCell(news: news[indexPath.row])
}
return cell
}
getGeneralNews() is a getter that returns an array of news
so what I'm doing in cellForRowAt is that I get the news in the given index path and configure my cell with it .
class NewsInfo {
static var sharedInstance = NewsInfo()
private init(){}
private (set) var generalNews:[OneNews]!{
didSet{
NotificationCenter.default.post(name:
NSNotification.Name(rawValue: "GeneralNewsIsSet"), object: nil)
}
}
func setGeneralNews(allGeneralNews:[OneNews]){
self.generalNews = allGeneralNews
}
func getGeneralNews() -> [OneNews]!{
return self.generalNews
}
}
Each news contains an array of the picture Ids
These are the fields in my OneNews class
var Subject :String!
var Content:String!
var ImagesId:[Int]!
Thanks !
UITableViewCell are reused as you scroll. When a cell goes off the top of the screen, it will be reused for another row appearing at the bottom of the screen.
UITableViewCell has a method prepareForReuse you can override. You can use that method to clear out iamgeViews or any other state that should be reset or cancel downloading of images.
In your case, you probably shouldn't use Data(contentsOf:) since it doesn't give you a way to cancel it. URLSessionDataTask would be a better option since it lets you cancel the request before it finishes.
You can try something like this. The main idea of this code is giving a unique number to check if the cell is reused.
I have renamed many properties in your code, as Capitalized identifiers for non-types make the code hard to read. You cannot just replace whole definition of your original NewsCell.
There was no declaration for imageSrc in the original definition. I assumed it was a local variable. If it was a global variable, it might lead other problems and you should avoid.
(Important lines marked with ###.)
class NewsCell: UITableViewCell {
#IBOutlet weak var images: ImageSlideshow!
#IBOutlet weak var subjectLbl: UILabel!
#IBOutlet weak var newsBodyLbl: UILabel!
//### An instance property, which holds a unique value for each cellForRowAt call
var uniqueNum: UInt32 = 0
func configureCell(news: OneNews) {
self.subjectLbl.text = news.subject
self.newsBodyLbl.text = news.content
let refNum = arc4random() //### The output from `arc4random()` is very probably unique.
self.uniqueNum = refNum //### Assign a unique number to check if this cell is reused
if news.imagesId.count==0 {
self.images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
} else {
var imageSrc: [ImageSource] = [] //###
for imgId in news.imagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
//### At this point `self` may be reused, so check its `uniqueNum` is the same as `refNum`
if self.uniqueNum == refNum, let d = data {
let img = UIImage(data: d)!
imageSrc.append(ImageSource(image: img))
self.images.setImageInputs(imageSrc)
}
}
}
}
}
self.images.slideshowInterval = 3
}
}
Please remember, the order of images may be different than the order of imagesId in your OneNews (as described in Duncan C's comment).
Please try.
If you want to give a try with this small code fix, without overriding the prepareForReuse of the cell, just change in configure cell:
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
// STUFF
}
in
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
if news.ImagesId.count > 0{
// STUFF
}
so every cell will start with the placeholderIcon even when reused
EDIT 2: Thought I had it but I don't. I need to create the array when the button is pressed, but I can't figure out how to access the .checkmark for each object. I have ".selected" as a property already, but can't access that either.
I have a UITableView that display a list in three sections with checkmarks. When a row in checked, the bool associated with that NSObject class changes from false to true. I have a button in the UINavigationBar that when pressed shows an alert with "Send" and "Cancel" options. When the user taps "Send" I want to have all the rows with checkmarks added into an array. I figured using that bool would be the best way, but in the func for the action I can't call the bool associated with each NSObject in the array. I included the parts of code I think are needed to help me.
EDIT: I believe this is how I have my class set up, unless I'm misunderstanding. One of the class' vars is "var selected: Bool" then when I create an Item from that class I set it to false. I have a list of Items that displays fine in cellForRowAt but when the button is tapped I can't get access to that same "sortedList". Here's more code. Maybe I'm still missing something else?
var arrayOfItemsSelected = [String]()
var sortedItems = [[Item]]()
var itemList: ItemList! {
didSet {
sortedItems = itemList.sortByBrands()
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = sortedItems[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "UIItemViewCell", for: indexPath)
cell.textLabel?.text = "\(item.name)"
cell.detailTextLabel?.text = "\(item.style)"
cell.accessoryType = cell.isSelected ? .checkmark : .none
cell.selectionStyle = .none // to prevent cells from being "highlighted"
return cell
}
func confirmButtonPressedAction() {
// Create the alert controller
let alertController = UIAlertController(title: "List of checked items", message: stringOfSelectedItems, preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "Send", style: UIAlertActionStyle.default) {
UIAlertAction in
self.arrayOfItemsSelected.removeAll()
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) {
UIAlertAction in
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.present(alertController, animated: true, completion: nil)
}
Your code either isn't doing anything with arrayOfItemsSelected or you're not showing us the relevant parts. As it is, the code sample will neither obtain nor process the selected items.
You could either implement the didSelectRowAt and didDeselectRowAt function to maintain an internal flag in your Item class, or you could get the selection from indexPathsForSelectedRows.
If you want to completely circumvent the UITableView's selection mechanism, you would need to handle cell clicking/tapping with actions in the UIItemViewCell itself (but that seems overly complicated).
BTW, I am doubtful that you can rely on cell.isSelected from a cell you just dequeued. Also, resorting and reloading the tableview in a didSet closure is likely to get you into trouble causing performance issues, random crashes or infinite loops.
If you have a property checked on the class Item you can try:
let itemsChecked = sortedItems.filter {
$0.checked == true
}
You have to add boolean check with each array object.link below
For Objective C:
#[
#{
#"Name":#"abc",
#"Status":[NSNumber numberWithBool:YES]
}
]
For Swift:
[
[
"Name": "abc",
"Status": true
]
]
You have to add boolean check with each array object, for example myModel.
[
"Name": "abc",
"Status": true
]
When a row in checked,
myModel.Status = myModel.Status ? false : true
myModel's default state = false
then " you can call the bool associated with each NSObject in the array"
I have a TableViewController in which selecting a row makes a network call, shows an activityIndicator, and in the completion block of the network call, stops the indicator and does a push segue.
In testing, I found that when going to the new view controller, going back, and selecting another row, it shows the activity indicator, but never stops or calls the push segue. The code for didSelect:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.userInteractionEnabled = false
self.searchController?.active = false
self.idx = indexPath
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
cell.accessoryView = self.activityIndicator
self.activityIndicator.startAnimating()
self.networkingEngine?.getObjectById("\(objectId)", completion: { (object) -> Void in
if object != nil {
self.tableView.userInteractionEnabled = true
self.chosenObject = object
self.activityIndicator.stopAnimating()
self.tableView.reloadRowsAtIndexPaths([self.idx], withRowAnimation: .None)
self.tableView.deselectRowAtIndexPath(self.idx, animated: true)
self.performSegueWithIdentifier("segueToNewVC", sender: self)
}
else {
}
})
}
and my network call using Alamofire
func getObjectbyId(objectId: String, completion:(Object?) -> Void) {
Alamofire.request(Router.getObjectById(objectId: objectId))
.response { (request, response, data, error) -> Void in
if response?.statusCode != 200 {
completion(nil)
}
else if let parsedObject = try!NSJSONSerialization.JSONObjectWithData(data as! NSData, options: .MutableLeaves) as? NSDictionary {
let object = parsedObject["thing"]
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(object)
})
}
}
}
So I made breakpoints, and it actually is going into the completion block the second time and calling it to stop indicator, reload that row, deselect the row, re-enable the tableView userInteraction, and perform the segue. It calls my prepareForSegue as well, but as soon as it finishes, it just sits with the indicator still spinning, and the RAM usage skyrockets up to almost 2GB until the simulator crashes.
I believe it has to do with multi-threading but I can't narrow down the exact issue since I'm putting my important stuff on the main thread.
The problem had to do with making the accessoryView of the cell an activityIndicator and then removing it and reloading it all while segueing away. I couldn't figure out how to keep it there and stop the multi-threading issue but I settled for a progress AlertView. If anybody has a solution, I'd love to know how this is fixed.