Swift changing image doesn't work - ios

EDIT: After changing the image to a label and transform a string ">" 90 degrees it is still not working correctly. I print out the values of the degrees and it works as expected but the transform is not happening.
I have a totally strange behaviour in my iOS App. I have a table view with section headers. In these section headers are typical dropdown images. One arrow down and one arrow up image. When I tap on the header section the cells display or hide dynamically. Now I want to change the image also according to that.
This is the code that runs when I tap the section header:
func selectedSection(sender: UIButton) {
let previousSelected = self.selectedSection
self.selectedSection = sender.tag
if previousSelected != self.selectedSection {
// one section opens another gets closed
// workaround so the animation does not look awful for section headers
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("Selected: \(self.selectedSection)")
sectionHeaders[self.selectedSection].toggleIcon()
print("***********")
var indexPathToInsert = [IndexPath]()
var indexPathToDelete = [IndexPath]()
for i in 0..<self.menuItems[self.selectedSection].getSubItems().count {
indexPathToInsert.append(IndexPath(row: i, section: self.selectedSection))
}
for i in 0..<self.menuItems[previousSelected].getSubItems().count {
indexPathToDelete.append(IndexPath(row: i, section: previousSelected))
}
tableView.beginUpdates()
tableView.deleteRows(at: indexPathToDelete, with: .none)
tableView.insertRows(at: indexPathToInsert, with: .automatic)
tableView.endUpdates()
} else {
// close the section so the selected section is 0 again
self.selectedSection = 0
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("***********")
tableView.reloadSections([previousSelected], with: .none)
}
}
Toggle Icon looks like this:
func toggleIcon() {
isOpen = !isOpen
print("\(isOpen)")
if isOpen {
print("Works")
self.sectionIcon.image = UIImage(named: "arrow-up")
} else {
print("Does not work")
self.sectionIcon.image = UIImage(named: "arrow-down")
}
}
When I run this and click on different headers everything works as expected. But as soon as I close one section without opening another this section is broken. The code still works but it does not change the image. It prints out:
'Selected: 1'
true
Works
And when I use a breakpoint the code is called where it sets the icon to arrow-up. However, it does not change the image.
I really don't understand what is happening. Is it not allowed to change an image more than once?
Thanks for your help!

I'm not sure if this is what cause the issue, but keep in mind that ALL UI Changes has be on the main thread.
on Swift 3,
try wrapping your call to update the image with something like
DispatchQueue.main.async { }
Good luck :)

Ok I was able to solve it with my own implementation.
I changed the else statement in selectedSection(sender: UIButton) to:
// close the section so the selected section is 0 again
self.selectedSection = 0
print("Previous: \(previousSelected)")
sectionHeaders[previousSelected].toggleIcon()
print("***********")
var indexPathToDelete = [IndexPath]()
for i in 0..<self.menuItems[previousSelected].getSubItems().count {
indexPathToDelete.append(IndexPath(row: i, section: previousSelected))
}
tableView.beginUpdates()
tableView.deleteRows(at: indexPathToDelete, with: .left)
tableView.endUpdates()
So animating the whole section did not work. Thanks #Mitesh jadav for his option but this does not close the other open sections so I sticked with my solution.

Related

Image not display well when added more than 2 same cells (dynamic tableView)

With my system I build my cell programmatically and set what I need in the cell compared to my array (data source of my tableView).
The problem appear when I add 3 cells or more on my tableView. The text is good but the image does not appear in the correct cell. I think this is a problem of the cache of the TableView system (reuse). I follow several post in this forum to fix that but nothing work.
This is my code in cellForRowAt :
let waitingCell = tableView.dequeueReusableCell(withIdentifier:"waiting", for: indexPath) as! CardCellWaiting
var cellToReturn : UITableViewCell!
let currentSurvey = user.lobbySurvey[indexPath.row]
let state : UserSurvey.state = currentSurvey.stateSurvey
switch state{
case .surveyWaiting:
cellToReturn = waitingCell
waitingCell.drawCard() // draw the card programmatically if needed
waitingCell.clearImage() // look below to see the function
waitingCell.setId(id: currentSurvey.id)
waitingCell.setImage(image : currentSurvey.picture)
waitingCell.setTimeLeft(timeLeft: currentSurvey.timeLeft)
waitingCell.delegate = self
waitingCell.delegateCard = self
}
return cellToReturn
this is how I update my source data (lobbySurvey) an array who contain User Survey classes. I build my cells since this one.
user.lobbySurvey.remove(at: 0)
self.tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
let surveyWaiting = UserSurvey(stateOfSurvey: .surveyWaiting)
surveyWaiting.picture = image
if let url = json["imageUrl"].string {
surveyWaiting.pictureUrl = url
}
if let timeLeft = json["timeLeft"].string {
surveyWaiting.timeLeft = timeLeft
}
if let surveySelfId = json["surveySelfId"].string {
surveyWaiting.id = Int(surveySelfId)
}
let rowToInsert = self.getRowToInsert(typeOfState: .surveyWaiting)
user.counterSurvey += 1
user.lobbySurvey.insert(surveyWaiting, at: rowToInsert)
self.tableView.insertRows(at: [IndexPath(row: rowToInsert, section: 0)], with: .fade)
this is the clearImage function :
func clearImage(){
surveyWaiting.imageEmptyView.image = #imageLiteral(resourceName: "backgroundEmptyImage")
}
and my setImage function :
func setImage(image : UIImage){
surveyWaiting.imageEmptyView.image = image.resized(targetSize: CGSize(width:100,height:125))
}
I've try to empty my imageView like :
surveyWaiting.imageEmptyView.image = nil
But it doesn't work. I've also try to use a framework like Nuke with the url of the image, but nothing.
Why does my code not order the image in the good cell?
Ok, my mistake... I use this framework to draw async blur to my imageView, but this plugin keep cache of the image who have blur.. So I just remove the cache of the blur and this solve the problem.

UITableview Flickering in ios 11 while working correctly in iOS 10.2

I am trying to update my cell size when I click on particular cell.My table gets update perfectly in iOS 10.2 but my table flickers when I run my code on iOS 11 .I am also getting warning in iOS 11 that
-[UIApplication application state] must be used from main thread.
So, I have reload my table in dispatch queue but nothing happens. I have multi-level XIBs like I have cell in which I have a table which is registering another cell which expands on click.
Below is my code sample when height expands my main table flicker.
func expandMessageTable(height : CGFloat,expendedRow:Int){
self.expandMessageHeight = height
self.msgExpRow = expendedRow
let row = cellMenuArr.index(of:"MessageXIB")
let path = IndexPath(row: row!, section: 0)
print(path)
homeTblView.reloadRows(at: [path], with: .fade)
}
Below is the fine working code i am using to reload cells -
DispatchQueue.main.async(execute: {
UIView.performWithoutAnimation {
self.tableView?.beginUpdates()
let contentOffset = self.tableView?.contentOffset
self.tableView?.reloadRows(at: [IndexPath(row: j, section: 0)], with: .automatic)
self.tableView?.setContentOffset(contentOffset!, animated: false)
self.tableView?.endUpdates()
}
})
Hope it helps.

Reload collectionview cells and not the section header

When trying to create collapsible UICollectionView sections, I update the number of items in the section dependent on its state. However, doing it this way, I reload the section which also reloads the section header aswell, and I get a very weird behavior when animating my image in the section header.
Essentially, reloading the section header when changing section items enables the UICollectionView to update the items but the section animate looks and behaves strange.
Without calling reloadSection, it allows for the proper animation but the items do not load.
self?.collectionView?.performBatchUpdates({
let indexSet = IndexSet(integer: section)
self?.collectionView?.reloadSections(indexSet)
}, completion: nil)
What is the fix for this?
You may try to extract the sequence of IndexPath in a specific section, then call reloadItems on such sequence, doing so:
extension UICollectionView {
func reloadItems(inSection section:Int) {
reloadItems(at: (0..<numberOfItems(inSection: section)).map {
IndexPath(item: $0, section: section)
})
}
}
so your code might be something like:
var updateSection = 0 // whatever
collectionView.performBatchUpdates({
// modify here the collection view
// eg. with: collectionView.insertItems
// or: collectionView.deleteItems
}) { (success) in
collectionView.reloadItems(inSection: updateSection)
}

Is it unsafe to call reloadData() after getting an indexPath but before removing a cell at that indexPath?

I'm trying to track down a difficult crash in an app.
I have some code which effectively does this:
if let indexPath = self.tableView.indexPath(for: myTableViewCell) {
// .. update some state to show a different view in the cell ..
self.tableView.reloadData()
// show nice fade out of the cell
self.friendRequests.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
The concern is that calling reloadData() somehow makes the indexPath I just retrieved invalid so the app crashes when it tries to delete the cell at that indexPath. Is that possible?
Edit:
The user interaction is this:
User taps a button [Add Friend] inside of table view cell <-- indexPath retrieved here
Change the button to [Added] to show the tap was received. <-- reloadData called here
Fade the cell out after a short delay (0.5s). <-- delete called here with indexPath from #1
I can change my code to not call reloadData and instead just update the view of the cell. Is that advisable? What could happen if I don't do that?
Personally, I'd just reload the button in question with reloadRows(at:with:), rather than the whole table view. Not only is this more efficient, but it will avoid jarring scrolling of the list if you're not already scrolled to the top of the list.
I'd then defer the deleteRows(at:with:) animation by some small fraction of time. I personally think 0.5 seconds is too long because a user may proceed to tap on another row and they can easily get the a row other than what they intended if they're unlucky enough to tap during the wrong time during the animation. You want the delay just long enough so they get positive confirmation on what they tapped on, but not long enough to yield a confusing UX.
Anyway, you end up with something like:
func didTapAddButton(in cell: FriendCell) {
guard let indexPath = tableView.indexPath(for: cell), friendsToAdd[indexPath.row].state == .notAdded else {
return
}
// change the button
friendsToAdd[indexPath.row].state = .added
tableView.reloadRows(at: [indexPath], with: .none)
// save reference to friend that you added
let addedFriend = friendsToAdd[indexPath.row]
// later, animate the removal of that row
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
if let row = self.friendsToAdd.index(where: { $0 === addedFriend }) {
self.friendsToAdd.remove(at: row)
self.tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .fade)
}
}
}
(Note, I used === because I was using a reference type. I'd use == with a value type that conforms to Equatable if dealing with value types. But those are implementation details not relevant to your larger question.)
That yields:
Yes, probably what's happening is the table view is invalidating stored index path.
To test whether or not it is the issue try to change data that is represented in the table right before reloadData() is called.
If it is a problem, then you'll need to use an identifier of an object represented by the table cell instead of index path. Modified code will look like this:
func objectIdentifer(at: IndexPath) -> Identifier? {
...
}
func indexPathForObject(_ identifier: Identifier) -> IndexPath? {
...
}
if
let path = self.tableView.indexPath(for: myTableViewCell)
let identifier = objectIdentifier(at: path) {
...
self.tableView.reloadData()
...
if let newPath = indexPathForObject(identifier) {
self.friendRequests.removeObject(identifier)
self.tableView.deleteRows(at: [newPath], with: .fade)
}
}

Temporarily Hiding a cell in UICollectionView Swift iOS

I've been trying this for hours with no luck. I have a UICollectionView collectionView. The collection view is basically a list with the last cell always being a cell with a big plus sign to add another item. I've enabled reordering with the following. What I'd like for it to do is when I start the interactive movement, the plus sign cell goes away, and then when the user is done editing, it appears again. This is a basic version of the code I have:
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.Began:
...
self.collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
removeAddCell()
case UIGestureRecognizerState.Changed:
case UIGestureRecognizerState.Ended:
...
collectionView.endInteractiveMovement()
replaceAddCell()
default:
collectionView.cancelInteractiveMovement()
}
}
func removeAddCell(){
print("Reloading data - removing add cell")
data_source.popLast()
self.collectionView.reloadData()
}
func replaceAddCell(){
print("Reloading data - replacing add cell")
data_source.append("ADD BUTTON")
self.collectionView.reloadData()
}
It's very rough pseudocode, but I can't even get the simplest version of this to work. With the code I have, it gives me the dreaded "Fatal error: unexpectedly found nil while unwrapping an Optional values" on the line where I reference the UICollectionViewCell after removing the items from the data source.
If anyone who has done something like this could share their approach I'd really appreciate it! Thank you!
-Bryce
You can do something like this:
func longPressed(sender: UILongPressGestureRecognizer) {
let indexPath = NSIndexPath(forItem: items.count - 1, inSection: 0)
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! YourCollectionViewCell
switch sender.state {
case .Began:
UIView.animateWithDuration(0.3, animations: {
cell.contentView.alpha = 0
})
case .Ended:
UIView.animateWithDuration(0.3, animations: {
cell.contentView.alpha = 1
})
default: break
}
}
this way it gradually disappears instead of abruptly.
I've done something like this. The data source for the collection view tracks a BOOL to determine whether or not to show the Add Item Cell. And call insertItemsAtIndexPaths: and deleteItemsAtIndexPaths: to animate the Add Item Cell appearing and disappearing. I actually use a Edit button to toggle the modes. But you can adapt this code to use your gesture recognizer.
basic code:
self.editing = !self.editing; // toggle editing mode, BOOL that collection view data source uses
NSIndexPath *indexPath = [self indexPathForAddItemCell];
if (!self.editing) { // editing mode over, show add item cell
if (indexPath) {
[self.collectionView insertItemsAtIndexPaths:#[indexPath]];
}
}
else { // editing mode started, delete add item cell
if (indexPath) {
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
}
}

Resources