UISearchBar cannot become first responder after UITableView did re-appear - xcode

I have an iPhone app for iOS8. UIPageViewController has a child view of UITableViewController, and that's where I put UISearchController.
When the UITableViewController is presented for the first time, I have no trouble activating the search by doing
[self.searchController.searchBar becomeFirstResponder];
However, once the view controller gets invisible either by pushing to another table view controller, or modally presenting a UIViewController (visually masking it), the searchBar can no longer become first responder after it reappears.
The search itself can be done by
[self.searchController setActive:YES];
but, there is no caret blinking this way (user has to tap inside searchBar to start typing).
I have almost identical UITableViewController with UISearchController without the incident. So, I must be missing something, but I would like your opinion on which direction I should pay attention on solving this.
Below is how I set the search controller.
#interface MonthTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
#property (nonatomic, strong) UISearchController *searchController;
UITableViewController *searchController = [[UITableViewController alloc]initWithStyle:UITableViewStylePlain];
searchController.tableView.dataSource = self;
searchController.tableView.delegate = self;
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchController];
self.searchController.searchResultsUpdater = self;
self.searchController.delegate = self;
self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.origin.x, self.searchController.searchBar.frame.origin.y, self.searchController.searchBar.frame.size.width, 44.0);
self.tableView.tableHeaderView = self.searchController.searchBar;
self.definesPresentationContext = YES;
// Hide search bar
CGRect myBounds = self.tableView.bounds;
myBounds.origin.y += self.searchController.searchBar.frame.size.height;
_lastlyAddedTableBoundY = self.searchController.searchBar.frame.size.height;
self.tableView.bounds = myBounds;

I solved it by going Xcode > Product > Analyze.
I simply forgot to add
[super viewWillAppear:(BOOL)animated];
inside -(void)ViewWillAppear:(BOOL)animated on the corresponding UITableViewController.
Hopefully, my mistake can save someone's time in the future.

Related

How can I work around this MapKit bug that causes duplicate callouts?

There's a bug in MapKit that can cause duplicate callout views on an annotation. If the timing is just right, an annotation view can get re-used while it is being selected and apparently just before the callout view is actually added to it. As a result, the old callout view gets stuck there, and the new callout will appear on top of or next to it. Here's what this can look like in an OS X app:
There's only one annotation on this map. If you click elsewhere on the map to deselect the annotation, only one of the callouts disappears. In some cases you might have two callouts with completely different information, which is where things get really confusing for someone using your app.
Here's the majority of a sample OS X project I put together that illustrates this bug:
#import MapKit;
#import "AppDelegate.h"
#import "JUNMapAnnotation.h"
#interface AppDelegate () <MKMapViewDelegate>
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet MKMapView *mapView;
#property BOOL firstPin;
- (void)placeAndSelectPin;
- (JUNMapAnnotation *)placePin;
- (void)clearPins;
#end
#implementation AppDelegate
- (IBAction)dropSomePins:(id)sender {
self.firstPin = YES;
[self placeAndSelectPin];
[self performSelector:#selector(placeAndSelectPin) withObject:nil afterDelay:0.0001];
}
#pragma mark - Private methods
- (void)placeAndSelectPin {
[self clearPins];
JUNMapAnnotation *annotation = [self placePin];
[self.mapView deselectAnnotation:annotation animated:NO];
[self.mapView selectAnnotation:annotation animated:YES];
}
- (JUNMapAnnotation *)placePin {
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(50.0,50.0);
JUNMapAnnotation *annotation = [[JUNMapAnnotation alloc] initWithCoordinate:coord];
annotation.title = #"Annotation";
annotation.subtitle = (self.firstPin) ? #"This is an annotation with a longer subtitle" : #"This is an annotation";
[self.mapView addAnnotation:annotation];
self.firstPin = NO;
return annotation;
}
- (void)clearPins {
[self.mapView removeAnnotations:self.mapView.annotations];
}
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[JUNMapAnnotation class]]) {
static NSString *identifier = #"annotationView";
MKPinAnnotationView *view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (view == nil) {
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
view.canShowCallout = YES;
NSLog(#"new annotation view");
} else {
view.annotation = annotation;
}
return view;
}
return nil;
}
#end
The same bug seems to exist in iOS, though I've had a tougher time recreating it there.
While I'm waiting on Apple to fix this, I'd like to work around it as much as possible. So far I've come up with a few possibilities:
Don't re-use annotation views. From what I can tell this seems like the only way to completely avoid the bug, but it seems pretty inefficient.
When an annotation view is re-used in mapView:viewForAnnotation:, remove all of its subviews. Currently it seems like the callout is the only subview, though it doesn't seem like a particularly safe hack. It also only sort of works—it doesn't prevent duplicate callouts from appearing, it just keeps them from sticking around forever. (When this bug first happens, there actually aren't any subviews yet.)
Combine both of those: if dequeueReusableAnnotationViewWithIdentifier: returns a view that has any subviews, ignore it and create a new one. This seems a lot safer than 2 and isn't nearly as inefficient as 1. But as with 2 it's not a complete workaround.
I've also tried adding deselectAnnotation:animated: in every place I can think of, but I can't find anything that works. I assume that once the annotation view is re-used, the MapView loses track of the first callout, so none of its normal methods will get rid of it.
this is a bit out of left field, but..
try registering the same cell class with 2 different reuse identifiers. in viewForAnnotation:, alternate between using each identifier when dequeueing a cell. this should prevent grabbing from the same queue twice in succession.

How to use Storyboard to make popover that can be used in code?

I'm building a collection of forms each of which contains several fields. Some of the fields are UITextFields that will display a date. I've created a new class called DatePickerTextField, a descendant of UITextField. When a DatePickerTextField is tapped I'd like for a UIDatePicker control to appear in a popover.
My question is how do I use the storyboard to implement the popover? I can do a segue when there is a specific, visible control in the scene. But how do I represent a generic popover in the scene that I can attach to any instantiated DatePickerTextField that becomes active?
You can create segue that is not connected to any control but I don't think that there would be way to specify anchor point for popover from code. Another option is to create ViewController that is not connected with any segue. When editing storyboard, create ViewController which will be placed in popover, select it and navigate to Utilities pane->Attributes Inspector. Set Size to Freeform, Status Bar to None, specify unique Identifier that will be used to instantiate ViewController from code. Now you can change the size of ViewController by selecting its View and navigating to Utilities pane->Size Inspector.
After that you can create popover from code:
- (IBAction)buttonPressed:(id)sender {
UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
[popover presentPopoverFromRect:anchor.frame
inView:anchor.superview
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
One caveat is that you need to hold reference to popover as ivar of your class, otherwise it'll crash because UIPopoverController would be released and deallocated after buttonPressed returns:
#interface MyViewController : UIViewController {
// ...
UIPopoverController *popover;
// ...
}
So, I had a similar issue, and in case others might benefit, I figured I'd share it, since I benefit so much from stackoverflow.
This solution allows you to set the anchor of a customizable popover segue. It also allows you to configure the segue to be modal or not (I could not find a way to prevent the segue by dimming the exterior context, so if someone knows how to do that, I would be interested in hearing it); this is accomplished by setting the passthrough views for the popover controller. I also added the capacity to specify a custom view, rather than the view of the source viewcontroller (since I needed this capacity); this portion is not critical to the solution.
DynamicPopoverSegue.h
#interface DynamicPopoverSegue : UIStoryboardPopoverSegue
#property BOOL isModal;
#property UIView* sourceView;
#property CGRect anchor;
#end
DynamicPopoverSegue.m
#implementation DynamicPopoverSegue
- (void)perform
{
if (!self.popoverController.popoverVisible)
{
UIViewController* dst = (UIViewController*)self.destinationViewController;
UIViewController* src = (UIViewController*)self.sourceViewController;
UIView* inView = _sourceView ? _sourceView : src.view;
self.popoverController.contentViewController = dst;
if (!_isModal)
{
[self.popoverController setPassthroughViews:[[NSArray alloc] initWithObjects:inView, nil]];
}
[self.popoverController presentPopoverFromRect:_anchor
inView:inView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
#end
Then you just set your segue to "Custom" in the storyboard, and set the segue class to "DynamicPopoverSegue". In my case, since I wanted to associate it with dynamic layers in a view, I could not set the anchor, so I created the segue by control clicking from the view controller icon in the bar beneath my view controller to the view controller I was using to present the popupover.
To call the popover segue:
[self performSegueWithIdentifier:#"MyPopoverSegue" sender:self];
And to configure the popover segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"MyPopoverSegue"])
{
DynamicPopoverSegue* popoverSegue = (DynamicPopoverSegue*)segue;
// set the anchor to wherever you want it to be
popoverSegue.anchor = _destinationFrame;
}
}
- (IBAction)pressItemChooseOprateRoom:(id)sender {
if (isPad){
// UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
_myPopover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
CGRect rc=[self getBarItemRc:sender];
[_myPopover presentPopoverFromRect:rc
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[MLControl shared].popover =self;
// [self perfformSegueWithIdentifier:SEGUE_POP_OPERATEROOM sender:self];
}else{
[self iphoneOpenOperateRoomList];
/* [self performSegueWithIdentifier:#"iPhonePushOperateRoom" sender:self];
*/
}
}
-(void)iphoneOpenOperateRoomList{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
// if (!index.showTabBar) {
// vc.hidesBottomBarWhenPushed = YES;
// }
[self.navigationController pushViewController:vc animated:YES];
}
Just used the answer from Jonnywho for my SWIFT project. In case you need it:
Here's the SWIFT version:
let anchor: UIView = sender
var viewControllerForPopover = self.storyboard?.instantiateViewControllerWithIdentifier("GameAboutViewController") as! UIViewController?
let popover = UIPopoverController(contentViewController: viewControllerForPopover!)
popover.presentPopoverFromRect(anchor.frame, inView: anchor, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
Add a UIView in the scene dock.
You can add it as a subview to any existing view on the view controller.
You can then toggle it's isHidden property as you require.
You can add multiple such subviews and create many such popups.
This technique will save you from setting up a new View Controller and using segues.

How to design the content of a UIScrollView in a nib easily

I have a scrollview which has to display a view larger than the available display area.
I want to easily design the user interface without moving the embedded view up and down every time I have to do some changes.
The problem is everything outside the visible area is invisible in IB.
Is there any switch or trick to make everything visible in IB?
UPDATE
I have posted another solution here which I think is simpler and better, and works in storyboards.
ORIGINAL
Create your scroll view in the nib with the appropriate superview, position, and size.
Next, create a completely separate, top-level UIView instance by dragging a UIView out of the palette and dropping it into the work area outside of any existing views. In the Attributes inspector, set the Size popup to “None” and make sure the Status Bar, Top Bar, and Bottom Bar are all set to None. Here's an example:
This new top-level view will be your content view. Give your view controller two outlets: scrollView and contentView:
#interface MyViewController
#property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIView *contentView;
#end
In the nib, wire up the scrollView outlet to the scroll view and wire up the contentView outlet to the content view.
Build your content view hierarchy inside the content view. Set its size as large as you need - it can be larger than 320x480 (as long as you have set all of its bars to None).
In your view controller's viewDidLoad, add contentView as a subview of scrollView and set scrollView.contentSize to the size of contentView:
#implementation MyViewController
#synthesize scrollView = _scrollView;
#synthesize contentView = _contentView;
- (void)viewDidLoad {
[super viewDidLoad];
[self configureScrollView];
}
- (void)configureScrollView {
CGSize size = self.contentView.bounds.size;
self.contentView.frame = CGRectMake(0, 0, size.width, size.height);
[self.scrollView addSubview:self.contentView];
self.scrollView.contentSize = size;
// If you don't use self.contentView anywhere else, clear it here.
self.contentView = nil;
// If you use it elsewhere, clear it in `dealloc` and `viewDidUnload`.
}

How to create NSTextField programmatically?

I want to create NSTextField programmatically. I am new to Mac App Development.
Can anyone help me in this ?
Update :
I tried this code
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *myView = [[NSView alloc] init];
NSRect frameRect = NSMakeRect(20,20,100,140);
NSTextField *myTextField = [[NSTextField alloc] initWithFrame:frameRect];
[myView addSubview:myTextField];
}
This does nothing. Please correct me if i'm wrong anywhere.
Thank you.
Creating UI elements programmatically is very simple in Cocoa.
It involves two steps:
Create the view and set a frame to it.
Add it as a subview to any super view.
Following snippet will you help you understand better.
NSRect frameRect = NSMakeRect(20,20,40,40); // This will change based on the size you need
NSTextField *myTextField = [[NSTextField alloc] initWithFrame:frameRect];
[myView addSubview:myTextField];
It looks like you're creating an NSTextField OK, but you're not adding it to anywhere visible in the view hierarchy. Your app probably contains one or more NSWindow instances; if you want a view to appear in a particular window, you should add it as a subview of that window's content view.

IOS: use navigation controller in a subview

In a view of my program I have a button and with this button I open a subview; this subview is a view with a tableview. I want go to another view when I push a row of the tableview so I want to make this with a navigation controller; how can I do this?
Your table view delegate will receive a tableView:didSelectRowAtIndexPath: message when you click on one of the table's rows.
You can put there your code to create the UINavigationController and push on to it your new view.
This sample code (from another answer of mine on S.O.) shows how you can do that:
UINavigationController* navigation = [[UINavigationController alloc] init];
iVkViewController *overviewViewController = [[iVkViewController alloc] init];
overviewViewController.title = #"First";
[navigation pushViewController:overviewViewController animated:NO];
This should help you getting things on track.
One side note: you might think of having a navigation controller from the very start, this would make your UI more "well-behaved", but this depends ultimately on your app requirements.
If UIViewController addSubView a view of UITableViewController,you want to push in the method of tableView:didSelectRowAtIndexPath,you should check if self.navigationController is nil. If it's nil,You probably should use
[self.parentViewController.navigationController pushViewController:controller animated:YES];
if self.parentViewController is also nil,Sometimes,you have to set a #property to point out the parentViewController In UITableViewController,like:
#property (nonatomic, weak) UIViewController *parentVC;
and in UIViewController:
UITableViewController *tableViewVC = [[UITableViewController alloc] init];
tableViewVC.parentVC = self;
In UITableViewController ,-tableView:didSelectRowAtIndexPath:
[self.parentVC.navigationController pushViewController:controller animated:YES];

Resources