I am using a custom uitableviewcell, which is written in Objective C (everything else I have is written in Swift) for the purpose of having easy customer slide buttons. I have a tableview which uses these cells, and I want it to update even when one of the slide buttons (think slide to delete button) is showing. This is my code here:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_sync(dispatch_get_main_queue()) {
self.tasksTableView.reloadData()
self.tasksTableView.cellForRowAtIndexPath(NSIndexPath(forRow: searchForTaskByName(taskSlices[selectedSlice].name), inSection: 0))!.contentView.addSubview(self.view1)
(self.tasksTableView.cellForRowAtIndexPath(NSIndexPath(forRow: searchForTaskByName(taskSlices[selectedSlice].name), inSection: 0)) as! SESlideTableViewCell).setSlideState(SESlideTableViewCellSlideState.Left, animated: true)
})
})
Now, the subview I am adding is a rectangle that originally covers the cell, but over time shrinks (kind of like a reverse loading bar). The third line is part of the custom code:
[UIView animateWithDuration: 0.0 delay: 0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//code with animation
CGRect frame = snapshotView.frame;
frame.origin.x = targetPositionX;
snapshotView.frame = frame;
m_slideState = slideState;
//[m_leftButtonGroupView layoutIfNeeded];
//[m_rightButtonGroupView layoutIfNeeded];
} completion:^(BOOL finished){
if (finished) {
if (animationId != m_slideAnimationId) {
return;
}
m_slideState = slideState;
if (slideState == SESlideTableViewCellSlideStateCenter) {
m_preparedSlideStates = SESlideStateOptionNone;
isMoved1 = NO;
[self cleanUpSlideView];
}
}
}];
This moves the cell to keep the button in view. Now, the problem is, the tableview has to reload to show the changes (update subviews??) and this causes the cell to shift back to starting position, then the cell is shifted back to button displaying position. This causes random flashes of the cell. I have already tried reloadCellsAtIndexPaths, same deal. I have tried to hide and unhide around the animations; using different threads; dispatch_sync and dispatch_async; removing the table from superview, updating, and putting back; UIView.animationsSetEnable(false) blocks; deleteCellsAtIndexPaths and insertCellsAtIndexPaths, NOTHING will work. This is reproducible on my iPhone 5 and simulator.
What I really need is to figure out what reloadData does so I can call only the bit I need, and hopefully not animate anything. All my animations used have time 0.0 and no delay. I am running a small counting up algorithm in the background, which is variable++ once every .01 seconds, if this matters. Thank you for any help you can give me, I have been working on this for two days now (I am fairly new to iOS development and I wanted to do my research before asking questions).
dispatch_sync(dispatch_get_main_queue()) { } is the source of your problem change it to dispatch_async(dispatch_get_main_queue()) { // reload table view } and your good to go
Well, I figured out a REALLY hacky fix. I created a duplicate UITableView, which I placed under the main one. I set the main one to get removed from superview and then put back with the reloadData call and other updates between these two calls. This would reveal the second tableview for a split second, and then 0.5 seconds after the first update I would update the second tableview to keep it current. This actually works. I can't believe it works and I am flabbergasted that it is necessary. I am still definitely looking for a legitimate solution and so I will not be accepting my answer for a while, in case someone can help me out.
[self.tableView registerNib:[UINib nibWithNibName:#"CellXibName" bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER];
I have the flash problem.I finally find that the cell are not reuse when table reload.
I have some query block when I set each cell, The cell always recreate make it flash.
Related
This code animates my view every time it's run, except for the first time. When the keyboard is displayed or hidden, a UIView is repositioned:
[UIView
animateWithDuration:0.26
animations:^{
[self setupActiveOverlayViewFrame];
}completion:nil];
-(void)setupActiveOverlayViewFrame {
float optimalOverlayHeight = [self.activePanel optimalHeight];
float realOverlayHeight = MIN(optimalOverlayHeight, self.displayView.frame.size.height);
if (self.activePanel.frame.size.height != realOverlayHeight) {
self.activePanel.frame = CGRectMake(self.activePanel.frame.origin.x, 0, self.activePanel.frame.size.width, realOverlayHeight);
}
self.activePanel.center = [self correctCenterForOverlay];
}
The method I posted is just to show that all it does is re-size and re-position it.
The first time this code is run, it doesn't animate. It just jumps into position. Every time after that, it animates correctly.
Its possible that the "keyboard did display" notification is being called before your view is fully set up, like if you've pushed a view controller onto a navigation controller and immediately give a text view focus on viewDidLoad
You can either keep track of when the keyboard is up or down and when viewDidAppear is called, check if the keyboard is up, and run it. Or you can postpone giving focus to a text view/field until viewDidAppear: is called.
I got the same problem. I want to advice you to check Panel center before(!) animating to find out has it right position at the first run. If it's not. Just set right position. Also try the next code
[self setupActiveOverlayViewFrame];
[UIView animateWithDuration:0.26
animations:^{
[self.view layoutIfNeeded];
}completion:nil];
P.S. If you are using autolayout better use constraints to move your views using the code above.
How can I make imageView property of my cells fly in one by one?
I can now make them fly in, but only all at once.
And is it good for performance to do something like this?
Thank you!
Use the indexPath.row to calculate a delay.
float animationDelay = 0.2f;
[UIView animateWithDuration:0.3f
delay:animationDelay * indexPath.row
options:0
animations:^{
cell.imageView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^{}];
This will delay every row by 0.2 seconds relative to the row above it. Of course this will only work when you initially load the table, but I guess this is what you're trying to animate? When you start scrolling and new cells are loaded, you probably should set the delay to zero. Otherwise the images will take a while to show up.
To achieve this I recommend introducing a BOOL ivar called loadingTheFirstTime that is set to YES initially (in the init or awakeFromNib methods). Then you can set that to NO when the table is shown the first time. When creating the cells you should then check for this variable first to see if you have to add a delay.
I supposed here that you are in cellForRowAtIndexPath:. Change your animation with that :
[UIView animateWithDuration: 3.0
delay: indexPath.row
options: UIViewAnimationCurveLinear
animations: ^{
cell.imageView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
}
completion:^(BOOL finished) {
}];
The animations will load with 1 sec of interval. If you're not in this function, set a tag to your cells when you create them, and put that tag in the delay.
Maybe set up a method where there is a for loop that runs through every cell and runs the animation for each time through the loop? Regarding performance, I have no idea. I would't imagine it would be GOOD for performance, especially if there are a lot of cells, but you can always try it and find out! :)
I'm trying to achieve an effect similar to what twitter does when you bring up the new tweet dialogue. They drop down a view from the top, shrinking the other views but still allowing you to interact with all of them if you dismiss the keyboard. It obviously isn't a modal view, but I can't put my finger on what the starting point to do something similar to this would be.
It looks straight-forward as a view hierarchy, just cleverly dressed with art. The bottom is the regular interface, above is a view containing the UITextView with some nice notepad art around it.
One way to achieve this is to hang two subviews under the view controller's main view. The first child contains the notepad art and the text view. It's positioned at 0,-NOTEPAD_HEIGHT. The second child is at 0,0 and occupies the entire parent view's bounds.
The compose button tells the text view to become first responder, and when editing begins...
- (void)textViewDidBeginEditing:(UITextView *)textView {
[self setNotepadHidden:NO animated:YES];
}
I often make a show/hide method of the following form to rearrange things like this ...
- (void)setNotepadHidden:(BOOL)hidden animated:(BOOL)animated {
NSTimeInterval duration = (animated)? 0.3 : 0.0;
CGFloat offset = (hidden)? -NOTEPAD_HEIGHT : NOTEPAD_HEIGHT;
[UIView animateWithDuration:duration animations:^{
self.firstChild.frame = CGRectOffset(self.firstChild.frame, 0.0, offset);
self.secondChild.frame = UIEdgeInsetsInsetRect(self.secondChild.frame, UIEdgeInsetsMake(offset, 0, 0, 0));
}];
}
Call with ...Hidden:YES whenever you want to hide it again. Make sure that the second child's subviews have autoresizing behavior setup so that they do the right thing when their parent shrinks.
I often find the need for one like this, also...
- (BOOL)isNotepadHidden {
return self.firstChild.frame.origin.y < 0.0;
}
Hopefully, that's a good start.
I have a UITableView that needs to introduce new content from the bottom. This is how a table view behaves when the view is full and you add new rows with animation.
I was starting it by altering the contentInset as rows are introduced but then when they change things go off, and the entry animation is all wrong... My problem with this approach is compounded by the fact that users can delete rows, and the row contents update, causing them to resize (each row has it's own height which changes).
Any recommendations on how to get a UITableView rows to always appear at the bottom of the UITableView's view space?
I've got a solution that works for me perfectly, but it causes a bunch of double thinking so it's not as simple in theory as it is in practice... kinda...
Step 1, apply a transform to the table view rotating it 180deg
tableView.transform = CGAffineTransformMakeRotation(-M_PI);
Step 2, rotate your raw cell 180deg in tableView:cellForRowAtIndexPath:
cell.transform = CGAffineTransformMakeRotation(M_PI);
Step 3, reverse your datasource. If you're using an NSMutableArray insert new objects at location 0 instead of using AddObject...
Now, the hard part is remembering that left is right and right is left only at the table level, so if you use
[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationLeft]
it now has to be
[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationRight]
and same for deletes, etc.
Depending on what your data store is you may have to handle that in reverse order as well...
Note: rotate the cells OPPOSITE the table, otherwise floating point innacuracy might cause the transform to get off perfect and you'll get crawlies on some graphics from time to time as you scroll... minor but annoying.
The accepted method introduces issues for my app - the scroll bar is on wrong side, and it mangles cell separators for UITableViewStyleGrouped
To fix this use the following
tableView.transform = CGAffineTransformMakeScale (1,-1);
and
cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
// if you have an accessory view
cell.accessoryView.transform = CGAffineTransformMakeScale (1,-1);
Similar approach to ima747, but rotating 180 degrees also makes the scrolling indicator go to the opposite side. Instead I flipped the table view and its cells vertically.
self.tableView.transform = CGAffineTransformMakeScale(1, -1); //in viewDidLoad
cell.transform = CGAffineTransformMakeScale(1, -1);//in tableView:cellForRowAtIndexPath:
Create a table header that is the height of the screen (in whatever orientation you are in) LESS the height of the of rows you have that you want visible. If there are no rows, then the header is the full height of the table view. As rows are added, simultaneously reduce the height of the table header by the height of the new row. This means changing the height of the frame of the view you provide for the table header. The point is to fill the space above the table rows to give the appearance that the rows are entering from the bottom. Using a table header (or section header) pushes the table data down. You can put whatever you like in the header view, even have it blank and transparent if you like.
This should have the effect you are looking for, I think.
Look at the attribute tableHeaderView. You simply set this to the view you want displayed in the table header. Then you can manipulate it as needed as you add rows. I can't recall just how forceful you then need to be to get the view to actually update in the UI. Might be as simple as calling setNeedsDisplay, if anything.
Alternatively, look at the methods tableView:viewForHeaderInSection: and tableView:heightForHeaderInSection:. Similar to using a table header view, you would want to have an instance variable that you setup once but that you can access from these methods to return either the view itself or its height, respectively. When you need to change the view for the (first) section, you can use reloadSections:withAnimation: to force an update to the view on screen after you have changed the views height (or content).
Any of that make sense? I hope so. :-)
Swift 3.01 - Other solution can be, rotate and flip the table view. Works very well for me and not mess with the animation and is less work for the reload data on the table view.
self.tableView.transform = CGAffineTransform.init(rotationAngle: (-(CGFloat)(Double.pi)))
self.tableView.transform = CGAffineTransform.init(translationX: -view.frame.width, y: view.frame.height)
I just wanted to add something to all of these answers regarding the use of this technique with UICollectionView... Sometimes when invalidating the layout, my cells would get transformed back the wrong way, I found that in the UICollectionViewCell and UICollectionReusableView subclasses I had to do this:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
[super applyLayoutAttributes:layoutAttributes];
[self setTransform:CGAffineTransformMakeScale(1, -1)];
}
dataSourceArray = dataSourceArray.reversed()
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
cell.transform = CGAffineTransform(scaleX: 1, y: -1)
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text {
dataSourceArray.insert(text, at: 0)
self.tableView.reloadData()
textField.text = ""
}
textField.resignFirstResponder()
return true
}
An easier way is to add the following lines at the bottom of cellForRowAtIndexPath
if(indexPath.section == self.noOfSections - 1)
[self scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self numberOfRowsInSection:self.noOfSections -1] - 1) inSection:self.noOfSections -1] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
Late to the party but, inspired by #Sameh Youssef's idea, a function to scroll to the last cell in the tableview, assuming you only have one section. If not, just return the number of sections instead of hardcoding the 0.
The microsecond delay was arbitrarily chosen.
func scrollToLast() {
DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(5)) {
let lastIndex = IndexPath(row: self.tableView.numberOfRows(inSection: 0) - 1, section: 0)
if lastIndex.row != -1 {
self.tableView.scrollToRow(at: lastIndex, at: .bottom, animated: false)
}
}
}
I would recommend to use the approach described in this blog post.
Have checked it on iOS 12 and 13. Works perfectly.
Well, if you load your tableview with an NSMutableArray i would suggest you to sort out the array in the inverse order. So the table view will be filled up like you want.
I would like to completely reset the scroll position of a UITableView, so that every time I open it, it is displaying the top-most items. In other words, I would like to scroll the table view to the top every time it is opened.
I tried using the following piece of code, but it looks like I misunderstood the documentation:
- (void)viewWillAppear:(BOOL)animated {
[tableView scrollToNearestSelectedRowAtScrollPosition:UITableViewScrollPositionTop animated:NO];
}
Is this the wrong approach here?
August got the UITableView-specific method. Another way to do it is:
[tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
This method is defined in UIScrollView, the parent class to UITableView. The above example tells it to scroll to the 1x1 box at 0,0 - the top left corner, in other words.
The method you're using scrolls to (as the method name implies) the nearest selected row. In many cases, this won't be the top row. Instead, you want to use either
scrollToRowAtIndexPath:atScrollPosition:animated:
or
selectRowAtIndexPath:animated:scrollPosition:
where you use the index path of the row you want to scroll to. The second method actually selects a row, the first method simply scrolls to it.
What I ended up doing was this:
Swift 4
self.tableView.contentOffset = CGPoint.zero
DispatchQueue.main.async {
self.tableView.reloadData()
}
Swift:
self.tableView.scrollRectToVisible(CGRect.zero, animated: false)