UITableView cell just disappeared callback? - ios

I have a UITableView with heavy images content. So the scrolling is not fluid anymore.
I want to add a timer to load the images, while you scroll I create the timer for each row. If the cell quits the view, I cancel the timer. If not I fade in the images.
My question is : is there a callback for a cell going out of view ? I'm reading the doc, but I'm not sure there is anything for my needs.
Thanks for the help !
EDIT: The code I'm using (this is the three20 library, I'm using a custom TTTableItemCell. The "_tabBar1.tabItems = item.photos" is the line hoging resources. On the first load it's okay because the photos are being loaded asynchronously from the server, but when I scroll back or reload the view, they are all loaded synchronously, and the scrolling isn't smooth anymore, especially on an iPhone 3G. :
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
Mission* item = object;
self.textLabel.text = item.name;
_tabBar1.tabItems = nil;
timerFeats = [NSTimer scheduledTimerWithTimeInterval:(0.5f) target:self selector:#selector(updateFeats) userInfo:nil repeats: NO];
//_tabBar1.tabItems = item.photos;
}
}
-(void)updateFeats {
DLog(#"timer ended");
Mission* item = self.object;
self._tabBar1.tabItems = item.photos;
}

If you're using iOS 6 and up, simply override this method:
- tableView:didEndDisplayingCell:forRowAtIndexPath:
It'll be called when the cell has already gone out of the view, and you'll get it and its indexPath.

Alright, I found a way.
There is actually a callback to know what cell is about to get out of view. :
- (void)willMoveToSuperview:(UIView *)newSuperview;
So my code is :
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if(!newSuperview) {
DLog(#"timer invalidated");
if ([timerFeats isValid]) {
[timerFeats invalidate];
}
}
}
If there is no newSuperview the cell is going out of the view and so I verify first that my timer hasn't been invalidated yet, and then I cancel it.

I suggest to use a KVO approach:
On your awakeFromNib method (or whatever method you use to instantiate the cell) add the following:
- (void)awakeFromNib {
[self addObserver:self forKeyPath:#"hidden" options:NSKeyValueObservingOptionNew context:nil];
...
}
Be sure to implement the delegate method for the observer as follow:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:#"hidden"]) {
NSLog(#"cell is hidden");
}
}

When a UITableView is initially shown it calls this method once for each row
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
after this the method is called when ever a new row is required which is often the result of scrolling to reveal a new row and pushing an old view off the other end.
So this is an ideal hook to find out which rows are visible. To check which cells are visible you can call
- (NSArray *)indexPathsForVisibleRows
Because the tableview is the only thing holding a reference to your cells before they are recycled or freshly creaetd you can not get a handle on those timers. What I suggest is creating an NSMutableDictionary ivar and when you create your cells add the timer to the NSMutableDictionary
[timersForIndexs setObject:yourTimer forKey:indexPath];
Now when you recieve - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath you need to do something like
NSMutableDictionary *tmpDictionary = [timersForIndexs copy];
[tmpDictionary removeObjectsForKeys:[self.tableView indexPathsForVisibleRows]];
NSArray *timers = [tmpDictionary allKeys];
[timers makeObjectsPerformSelector:#selector(invalidate)];
I'm not in front of xcode so this is dry coded so please let me know if you have any problems

Related

Crash - UITableViewCell subclass KVO'ing UITableView.panGestureRecogonizer.state

Situation:
I've subclassed UITableViewCell because I need to add custom action buttons on either side of the UITableViewCell. There are certain situations where I need to set the UITableView back to normal (hide the custom action buttons). e.g. When the user scrolls upwards in the UITableView. To do this I am adding my custom UITableViewCell as an observer of the containing UITableView's UIPangestureRecognizer's state.
Problem:
When popping the UIViewController that contains the UITableView and custom UITableViewCells I receive the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7b21b920 of
class UIScrollViewPanGestureRecognizer was deallocated while key value
observers were still registered with it. Current observation info:
( Context: 0xb83618, Property: 0x7b3e13b0>
Context: 0xb83618,
Property: 0x7b3e13b0>
Context: 0xb83618, Property: 0x7b3e13b0> Context: 0xb83618, Property: 0x7b3e13b0> )'
Which is obviously saying that the UIPanGestureRecognizer is being deallocated before the custom UITableViewCell's are.
Question:
Where should I remove the custom UITableViewCell as an observer of the UITableView's UIPanGestureRecognizer so I don't encounter this exception?
Code: (I hope this isn't too much code to comb through. I apologize if it is.)
CustomUITableViewCell.m
#pragma mark - Setter Methods
- (void)setContainingTableView:(UITableView *)containingTableView
{
if (self.isObservingContainingTableViewPanGestureRecognizer)
{
self.observingContainingTableViewPanGestureRecognizer = NO;
[_containingTableView.panGestureRecognizer removeObserver:self forKeyPath:kUITableViewPanGestureRecognizerStateKeyPath];
}
_containingTableView = containingTableView;
if (containingTableView)
{
self.observingContainingTableViewPanGestureRecognizer = YES;
[containingTableView.panGestureRecognizer addObserver:self forKeyPath:kUITableViewPanGestureRecognizerStateKeyPath options:0 context:UITableViewPanGestureRecogonizerContext];
}
}
#pragma mark -
#pragma mark - Overrides
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
self.containingTableView = nil;
UIView * view = self.superview;
while (view)
{
if ([view isKindOfClass:[UITableView class]])
{
self.containingTableView = (UITableView *)view;
break;
}
view = view.superview;
}
}
- (void)dealloc
{
self.containingTableView = nil;
}
#pragma mark -
#pragma mark - Key Value Observing
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == UITableViewPanGestureRecogonizerContext)
{
if ([keyPath isEqual:kUITableViewPanGestureRecognizerStateKeyPath])
{
UIPanGestureRecognizer * panGestureRecognizer = (UIPanGestureRecognizer *)object;
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint velocity = [panGestureRecognizer velocityInView:self.contentCellView];
if (fabs(velocity.y) >= fabs(velocity.x))
{
[self.scrollView setContentOffset:CGPointZero animated:YES];
}
}
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark -
As always, any help is greatly appreciated! Also, if anyone needs any more information please let me know.
EDIT:
Oddly enough, the custom UITableViewCell's dealloc method is called and the custom UITableViewCell is removed as an observer before I the exception is thrown.
It turns out that I needed to keep a reference to the UITableView's UIPanGestureRecognizer. More than likely I'll end up subclassing UITableView to eliminate some of the complications.
UITableView is subclass of UIScrollView. If you only want to detect when user is scrolling it you can use scrollview delegate method:
(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
I think you should pass weak reference of table view to cell (as delegate) in cellForRowAtIndexPath rather than looking for the tableview in didMoveToSuperview. Overall, I don't think it's a good idea to have table view's gesture observer in cell. However if you really want it, make sure you register/unregister properly.
Also make sure isObservingContainingTableViewPanGestureRecognizer flag's initial value is right when the cell gets reused.

iOS Update Reload Contents Timer

In my VC I have a UITableView. Each cell has a UITableView as one of its contents. Timer is set updating each cell every 10secs. Events are handled which also reloads the respective cell.
Method that timer calls :-
-(void) updateVisitotsLists {
NSLog(#"UPDATING VISITORS LIST ***************************");
// Call API's to get lists
[api getVisitorsList];
// Init Arrays
browsingList = [MainAppDataObject sharedAppDataObject].visitors_browsingList;
secondList = [MainAppDataObject sharedAppDataObject].visitors_secondList;
thirdList = [MainAppDataObject sharedAppDataObject].visitors_thirdList;
fourthList = [MainAppDataObject sharedAppDataObject].visitors_fourthList;
// AS these are no more useful, so make it nil & save memory
[MainAppDataObject sharedAppDataObject].visitors_browsingList = nil;
[MainAppDataObject sharedAppDataObject].visitors_secondList = nil;
[MainAppDataObject sharedAppDataObject].visitors_thirdList = nil;
[MainAppDataObject sharedAppDataObject].visitors_fourthList = nil;
// Reload all lists with latest data
[self reloadBrowsingRow];
[self reloadSecondRow];
[self reloadThirdRow];
[self reloadFourthRow];
}
Event Handler Method :-
-(void) handleNewVisitor : (NSNotification *) notification {
// UPDATE VISITOR'S LIST
Visitor *v = [notification object];
#try {
if (v != nil) {
// Add V to browsing list
[browsingList addObject:v];
// Reload browsing list
[self reloadBrowsingRow];
}
}#catch (NSException *e) {
NSLog(#"EXCEP - %#", e);
}
v = nil;
return;
}
Reloading Method -
-(void)reloadBrowsingRow {
// Browsing
VisitorsListsCell *bcell = (VisitorsListsCell*)[self.visitorlistsTv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
[bcell.listsTableView reloadData];
[bcell updateButtonText];
[bcell setNeedsDisplay];
bcell = nil;
return;
}
The Problem :-
When updateVisitotsLists is called thru timer, the updated contents are not reflected on cell.
When event handler method calls the same [self reloadBrowsingRow]; method, the contents of the cell are updated and reflected.
Due to this despite cells contents are updated but are not reflected until the state of cell is changed - expanded or collapsed.
I tried removing timer and cell updates properly on event caught, but when timer was on and event was caught, method is called but contents are not reflected on the screen.
I feel both methods may be calling reload method at same time, hence this must be happening or what ? How can this be handled making sure that the contents of cells are updated in any respect ? Any help is highly appreciated. Thanks.
Use [tableview reloadData]; on that method.
Because Reload on tableView will reload all data, so it will be more helpful.

iOS GUI refresh

I am using setNeedsDisplay on my GUI, but there update is sometimes not done. I am using UIPageControllView, each page has UIScrollView with UIView inside.
I have the following pipeline:
1) application comes from background - called applicationWillEnterForeground
2) start data download from server
2.1) after data download is finished, trigger selector
3) use dispatch_async with dispatch_get_main_queue() to fill labels, images etc. with new data
3.1) call setNeedsDisplay on view (also tried on scroll view and page controller)
Problem is, that step 3.1 is called, but changes apper only from time to time. If I swap pages, the refresh is done and I can see new data (so download works correctly). But without manual page turn, there is no update.
Any help ?
Edit: code from step 3 and 3.1 (removed _needRefresh variables pointed in comments)
-(void)FillData {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stateID = [DataManager ConvertStateToStringFromID:_activeCity.actual_weather.state];
if ([_activeCity.actual_weather.is_night boolValue] == YES)
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#_noc", [_bgs objectForKey:stateID]]];
if (_isNight == NO)
{
_bgTransparencyInited = NO;
}
_isNight = YES;
}
else
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#", [_bgs objectForKey:stateID]]];
if (_isNight == YES)
{
_bgTransparencyInited = NO;
}
_isNight = NO;
}
[self.contentBgImage setNeedsDisplay]; //refresh background image
[self CreateBackgroundTransparency]; //create transparent background if colors changed - only from time to time
self.contentView.parentController = self;
[self.contentView FillData]; //Fill UIView with data - set labels texts to new ones
//_needRefresh is set to YES after application comes from background
[self.contentView setNeedsDisplay]; //This do nothing ?
[_grad display]; //refresh gradient
});
}
And here is selector called after data download (in MainViewController)
-(void)FinishDownload:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[_activeViewController FillData]; //call method shown before
//try call some more refresh - also useless
[self.pageControl setNeedsDisplay];
//[self reloadInputViews];
[self.view setNeedsDisplay];
});
}
In AppDelegate I have this for application comes from background:
-(void)applicationWillEnterForeground:(UIApplication *)application
{
MainViewController *main = (MainViewController *)[(SWRevealViewController *)self.window.rootViewController frontViewController];
[main UpdateData];
}
In MainViewController
-(void)UpdateData
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(FinishForecastDownload:) name:#"FinishDownload" object:nil]; //create selector
[[DataManager SharedManager] DownloadForecastDataWithAfterSelector:#"FinishDownload"]; //trigger download
}
try this:
[self.view performSelectorOnMainThread:#selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
or check this link:
http://blackpixel.com/blog/2013/11/performselectoronmainthread-vs-dispatch-async.html
setNeedsDisplay triggers drawRect: and is used to "redraw the pixels" of the view , not to configure the view or its subviews.
You could override drawRect: and modify your labels, etc. there but that's not what it is made for and neither setNeedsLayout/layoutSubviews is.
You should create your own updateUI method where you use your fresh data to update the UI and not rely on specialized system calls meant for redrawing pixels (setNeedsDisplay) or adjusting subviews' frames (drawRect:).
You should set all your label.text's, imageView.image's, etc in the updateUI method. Also it is a good idea to try to only set those values through this method and not directly from any method.
None of proposed solutions worked. So at the end, I have simply remove currently showed screen from UIPageControllView and add this screen again. Something like changing the page there and back again programatically.
Its a bit slower, but works fine.

Detect change of UILabel's text

I have a time that repeats itself every .1 seconds (I do this because I need to constantly look for a change on the website's text I am pulling from). My problem is that I need to be able to tell when the text changes from one word to another. So when the user open the app and let's say the text is displaying one song title but then it changes to a different one, Where it changes, I need to be able to detect it and preform an action.
Also it has to be when the app first loads it will not preform the action but when the text changes it will display it(It has to be this because I am pulling song titles). So I need to be able to change the duration of each song, but if a song is playing and they open the app in the middle of it, the duration will be mixed up. so I have two labels, one that shows text and another that shows the time. When the app first loads up, I want it to display nothing until the text in the song title changes, then pulls the duration from a if statement (below). It might be a little confusing but ask any question you need and I will try to explain.
Here is my code for pulling from the website and anything else that might help:
- (void)viewDidLoad {
[super viewDidLoad];
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(recentTracksText) userInfo:nil repeats:YES];
}
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
continuousLabel.text = self.strippedTextForBlog;
}
if ([continuousLabel.text isEqual: #"Lady Gaga - Gypsy"]) {
imageView1.image = [UIImage imageNamed:#"Lady Gaga - Gypsy.jpg"];
imageView.hidden = YES;
[self.view insertSubview:toolbar belowSubview:imageView];
[toolbar insertSubview:darkView belowSubview:imageView];
[self.view insertSubview:backgroundImage belowSubview:toolbar];
if([darkView.subviews containsObject:continuousLabel]) {
} else{
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
[darkView addSubview:Label];
[Label setText:#"1:30"];
[darkView addSubview:continuousLabel];
});
}
}
UPDATE
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
if (![self.strippedTextForBlog isEqualToString:continuousLabel.text])// this does not find when the text changes {
[self.pLabel setHidden:NO];
[self.pLabel setProgress:1
timing:TPPropertyAnimationTimingEaseOut
duration:150.0
delay:0.0]; //This does not go smoothy.
// this does not
}
continuousLabel.text = self.strippedTextForBlog;
}
Use Key-value observing if you want to observe the property directly
[continuousLabel addObserver:self
forKeyPath:#"text"
options:0
context:NULL];
Make sure you implement the observing method
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Insert your code here to deal with the change.
}
and a little bit of ARC management to taste:
- (void)dealloc {
NSLog(#"dealloc %#", [self class]);
if (continuousLabel) {
[continuousLabel removeObserver:self forKeyPath:#"text"];
}
}
Well, you already have the NSTimer that fires each 0.1 seconds, so you just have to check if the text wasn't changed.
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
if (![self.self.strippedTextForBlog isEqualToString:continuousLabel.text]) {
//The text isn't the same. Do something.
}
continuousLabel.text = self.strippedTextForBlog;
}

UISearchDisplayController - how to preload searchResultTableView

I want to show some default content when the user taps the Searchbar, but before any text is entered.
I have a solution working using settext:
- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
[searchDisplayController.searchBar setText:#" "];
}
This works but it's not elegant and the hint text in the Searchbar disappears.
Is there a better way to preload data to the SearchResultTableView in a UISearchDisplayController, without having to implement the whole UISearch functionality yourself in a custom controller?
For a demo of the desired effect, look at Safari's search box, if you tap it, the search interface opens with previous searches showing.
OK, I have it. Preload your dataSource with some data and do the following hack (nothing illegal) and you'll get the tableView to show up. Note that there may be some clean-up to do, but this will get your default data to display.
In viewDidLoad of the view controller than owns the UISearchBar:
[super viewDidLoad];
[self.searchDisplayController.searchResultsTableView reloadData];
// whatever you named your search bar - mine is property named searchBar
CGRect testFrame = CGRectMake(0, 20, self.searchBar.frame.size.width, 100);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.searchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
Here's what's happening:
The UISearchBar doesn't want to show the searchResultsTableView until you start editing. If you touch the tableView (e.g. reloadData) it will load and be there, sitting in memory with frame = CGRectZero. You give it a proper frame and then add it to the superview of the searchBar, et voila.
I verified this is correct by displaying the superview of the searchBar and the superview of the tableView after a proper load of the tableView - they have the same superview. So, I go back and add the tableView to the superview early, and sure enough it shows up. For me, it showed up dimmed (maybe another view above it? alpha set lower?), but was still clickable. I didn't go any further, but that definitely gets you your tableView displaying without any user interaction.
Enjoy,
Damien
I found a much better solution to this issue, and it seems to work perfectly on iOS 6 and 7. While it is still a hack, its a much cleaner and future proof hack than the above. The other solutions do not work consistently and prevent some UISearchDisplayDelegate methods from ever firing! Further I had complex insetting issues which I could not resolve with the above methods. The main issue with the other solutions is that they seriously confuse the internals of the UISearchDisplayController. My solution is based on the observation that UISearchDisplayContoller is a UISearchbarDelegate and that the automatic undimming & showing of results table can be triggered by simulating a keypress in the search field! So:
- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
if ([controller respondsToSelector: #selector(searchBar:textDidChange:)])
[(id<UISearchBarDelegate>)controller searchBar: controller.searchBar textDidChange: #" "];
}
This code is future proof against crashing by checking it responds to the UISearchbarDelegate method, and sends space #" " to trick the UISearchDisplayController into thinking user has typed a letter.
Now if the user types something and then erases it, the table will dim again. The other solutions try to work around this by doing something in the searchDisplayController:didHideSearchResultsTableView: method. But this doesn't make sense to me, as surely when you cancel the search it will need to truly hide your results table and you may need to run code in this case. My solution for this part is to subclass (note you could probably use a Method Swizzled Category to make it work everywhere if needed in your project):
// privately declare protocol to suppress compiler warning
#interface UISearchDisplayController (Super) <UISearchBarDelegate>
#end
// subclass to change behavior
#interface GMSearchDisplayController : UISearchDisplayController
#end
#implementation GMSearchDisplayController
- (void) searchBar: (UISearchBar *) searchBar textDidChange: (NSString *) searchString
{
if (searchString.length == 0)
searchString = #" ";
if ([super respondsToSelector: #selector(searchBar:textDidChange:)])
[super searchBar: searchBar textDidChange: searchString];
}
#end
This code works by intercepting the textDidChange delegate method and changing nil or empty strings in to space string #" " preventing the normal hiding/dimming that occurs on an empty search bar. If you are using this second bit of code, then you could modify the first bit to pass a nil instead of #" " as this second bit will do the needed conversion to #" " for you.
In my own project, I needed to handle the case that user does type a space, so instead of #" " above I used a defined token:
// arbitrary token used internally
#define SEARCH_PRELOAD_CONDITIONAL #"_#preresults#_"
And then handle it internally by converting it back to nil string:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
if ([searchString isEqualToString: SEARCH_PRELOAD_CONDITIONAL])
searchString = nil;
}
Enjoy! :)
Working solution for iOS 7:
// When the tableView is hidden, put it back
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
[self.searchDisplayController.searchResultsTableView reloadData];
controller.searchResultsTableView.hidden = NO;
// Also, remove the dark overlay
for (UIView *v in [[controller.searchResultsTableView superview] subviews]) {
// This is somewhat hacky..
if (v.alpha < 1) {
[v setHidden:YES];
}
}
}
-(void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
[self.searchDisplayController.searchResultsTableView reloadData];
if (self.searchDisplayController.active == YES) {
tableView.hidden = NO;
}
}
I have the solution. Insert these three methods.You have to have the tableview preloaded with the data in order this to work. I have this codes working in my code so it has to work for you as long as you have already preloaded the data and you are using search display controller.
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
controller.searchResultsTableView.hidden = YES;
}
This works in iOS 8:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController.searchResultsTableView.superview.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView.superview];
CGRect frame = self.searchDisplayController.searchResultsTableView.frame;
self.searchDisplayController.searchResultsTableView.frame = CGRectMake(frame.origin.x, 64, frame.size.width, frame.size.height);
}
However, I did not like this hack and replaced the searchDisplayController by my own implementation (UISearchBar with UITableView).
I tested the following in iOS 9 with UISearchController.
By default, whenever there is no text in the UISearchBar, the table shown will be your original table view (referred to as originalTableView from here on out). This may or may not have a black see-through layer depending on the value of dimsBackgroundDuringPresentation.
Once the user enters any text into the UISearchBar, the contents of the new table view will be shown (I will refer to this as the searchTableView).
Note that in both of these solutions, I'm implementing the UISearchController in a manner similar to how Apple does so in this example; namely, there are two separate UITableViewController's in charge of showing the data.
Solution 1: Complicated implementation, smoother animation
For a smoother animation:
Implement the UISearchControllerDelegate's willPresentSearchController and willDismissSearchController methods.
In willPresent, update your originalTableView's data to get ready to display whatever zero text data you want, then call reloadData().
In willDismiss, revert the process to show the original contents and call reloadData().
Example:
// here tableView refers to originalTableView
extension ViewController: UISearchControllerDelegate {
func willPresentSearchController(searchController: UISearchController) {
data = ["A", "B", "C"]
tableView.reloadData()
}
func willDismissSearchController(searchController: UISearchController) {
data = ["a", "b", "c"]
tableView.reloadData()
}
}
In this fictional example, the data is displayed as a,b,c when the user is not focused on the UISearchBar, but is displayed as A,B,C once the user taps on the UISearchBar.
Solution 2: Quick to implement, choppy animation
Instead of implementing the logic for the search controller in both originalTableView and searchTableView, you can use this solution to simply show the searchTableView even though it is hidden by default.
Most likely, you are already implementing the UISearchResultsUpdating protocol. By simply calling tableView.hidden = false in that method, you should get the behavior you are after; albeit with a less than stellar animation.
// here tableView refers to searchTableView
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
tableView.hidden = false
filterData(text: searchController.searchBar.text!)
tableView.reloadData()
}
}
Why don't you have a look at the TableSearch project that comes in Xcode? It more or less address what you need. It shows a table right at the beginning and shades it out when search field is tapped. Check TableSearch in Documentation and API reference in Xcode 4 under help.
Also take a look at this It almost does what you need with a UISearchBar and a UITableView. I'd prefer that approach instead. Tweak it.
And to address your needs further, I'd suggest using two datasources. One holding all the contents (this is what you would show at the very beginning) and one to hold the filtered search results. Now go ahead and empty the first datasource and fill it up with the contents of the second one to store & dim-show the results of the previous search. Proceed this way. Use this approach in the TableSearch code for your task.
Here's a way to pre-populate the searchController.
// 1. Save previous search results as soon as user enters text
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
// save searchString
return YES;
}
// 2. load previous search string at startup
- (void)viewDidLoad
{
// create data source for table view
// searchData may be a NSMutableArray property defined in your interface
self.searchData = // search your master list for searchString
// or create your own list using any search criteria you want
// or you may act depending on user preference
if(!showPreviousSearch)
{
// [searchData removeAllObjects];
}
}
// 3. provide correct data source when asked
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
...
}
NSMutableArray *dataSource = nil;
if (tableView == [[self searchDisplayController] searchResultsTableView])
{
dataSource = self.searchData;
}
else
{
dataSource = self.masterData;
}
...
return cell;
}
// Hope this helps.
Thanks

Resources