Adding UIGestureRecognizer to view inside UIScrollView - uiscrollview

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(textTapped:)];
tap.delegate = self;
tap.numberOfTapsRequired = 1;
tap.cancelsTouchesInView = NO;
for(PaginationView* page in self.flashView._innerScrollView.subviews) {
[page addGestureRecognizer:tap];
}
I have a custom view called a PaginationView and I have several of them in a UIScrollView.
I'd like to add a gesture recognizer for each of them.
It appears to add the gesture recognizer to the PaginationViews fine, but the "textTapped" method is never called.
My delegate implements this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
And this is set in my PaginationView init method:
self.userInteractionEnabled = YES;
Thanks!

Related

NSTextView keyDown Event

I want trigger the keyDown Event with every Key action. Therefore I have created a subclass of NSView.
#interface CodrTextView : NSView {
NSTextField *lineNumberSpace;
NSTextView *mainTextView;
}
- (id) initWithTextView:(NSTextView *)textView lineNumberSpace:(NSTextField *)textField;
I already have the method's:
- (id) initWithTextView:(NSTextView *)textView lineNumberSpace:(NSTextField *)textField {
self = [self init];
if (self){
mainTextView = textView;
lineNumberSpace = textField;
}
return self;
}
- (BOOL) acceptsFirstResponder {
return YES;
}
- (BOOL)canBecomeKeyView {
return YES;
}
My plan is to count the lines in the textView and write the numbers in the lineNumberSpace. I know that the methods work, because I have test it already with an IBAction on a button. This is the method:
- (long) getLineCount{
NSString *content = [mainTextView string];
NSUInteger numberOfLines, index, contentLength = [content length];
for (index = 0, numberOfLines = 0; index < contentLength; numberOfLines++){
index = NSMaxRange([content lineRangeForRange:NSMakeRange(index, 0)]);
}
NSLayoutManager *layoutManager = [mainTextView layoutManager];
NSUInteger numberOfGlyphs =[layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
(void) [layoutManager lineFragmentRectForGlyphAtIndex:index
effectiveRange:&lineRange];
index = NSMaxRange(lineRange);
}
numberOfLines++;
return numberOfLines;
}
- (void)keyDown:(NSEvent *)event {
NSMutableString *lineNumberString = [NSMutableString string];
long numberOfLines = [self getLineCount];
for (int i = 1; i <= numberOfLines; i++){
[lineNumberString appendString:([NSString stringWithFormat:#"%d \n", i])];
}
[lineNumberSpace setStringValue:lineNumberString];
}
This method works surely. The problem is, with a button the number in the lineNumberSpace is changing correctly but with the keyDown Event it don't work. What is my mistake here?
Ahhh, looking at your code I now realize what the problem is.
Your "CodrTextView" object is not subclassed from "NSTextView" (and also, when you instantiate the object programatically or via your XIB or Storyboard, make sure the text view object is a custom class of "CodrTextView" and not just another "NSTextView"), so it's not actually getting the "acceptsFirstResponder" or "canBecomeKeyView" method calls either.
You need to descend "CodrTextView" from "NSTextView" instead of "NSView", OR you need to create another subclassed "NSTextView" object which will receive the "keyDown:" event and then it'll call the code that calculates the string that goes into "lineNumberSpace" of your main view.
Does this make sense to you now?

How to add action to a barbuttonitem in a navcontroller that was created programmatically within tabcontroller

I attempted to add the barbuttonitem into the xcode proj and finally got it to show up using the view controller and putting the code in the view did load section but I cannot connect the action... I have always used interface builder and IBOutlets but that is not an option as this navigation controller had to be programmatically put in to have the tab bar controller work
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//add navbar buttons progamatically b/c nothing to use in xibs...
UIBarButtonItem * popbutt = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(apopbuttonPressed:)];
UINavigationItem * navigItem = [[UINavigationItem alloc]initWithTitle:#"Scribbler"];
navigItem.rightBarButtonItem = popbutt;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
This is my first attempt at putting actions together without using IBOutlet and interface builder so I'm sure it looks pretty bad the goal is to use FPPopover and have the nav bar button item display a popover when pressed
-(void)apopbuttonPressed
{
//the controller we want to present as popover
ScribblePopoverViewController * controller = [[ScribblePopoverViewController alloc] initWithStyle:UITableViewStylePlain];
controller.delegate = self;
apop = [[FPPopoverController alloc] initWithViewController:controller];
//apop.arrowDirection = FPPopoverArrowDirectionAny;
apop.tint = FPPopoverDefaultTint;
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
apop.contentSize = CGSizeMake(300, 500);
}
else {
apop.contentSize = CGSizeMake(200, 300);
}
apop.arrowDirection = FPPopoverArrowDirectionAny;
//sender is the UIButton view
// idk [apop presentPopoverFromView:];
}
you set the selector to "apopbuttonPressed:" <- the ending : says it expects a parameter
your method instead is "apopbuttonPressed" without any parameter.
So try
action:#selector(apopbuttonPressed)
or change your method to
apopbuttonPressed:(id) sender
EDIT: Here is a working code snippet:
this is my code and it works fine:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem * popbutt = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(apopbuttonPressed:)];
self.navigationItem.rightBarButtonItem = popbutt;
}
- (void) apopbuttonPressed:(id) sender
{
NSLog(#"pressed");
}

How to enable implicit animation in UIView when doing custom drawing?

I perform custom drawing in my UIView subclass using:
- (void)drawRect:(CGRect)rect;
How can I enable implicit animation when the drawing changes?
Since there were no other suggestions I went ahead with the layer delegate method. Perhaps that's the only option anyway.
So in MyView I created a new CALayer property:
-(CALayer*)stylesLayer
{
if (_stylesLayer == nil) {
CALayer *aLayer = [CALayer layer];
aLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
if ([self respondsToSelector:#selector(setContentScaleFactor:)])
{
aLayer.contentsScale = self.contentScaleFactor;
}
_stylesLayer = aLayer;
[_stylesLayer retain];
[self.layer addSublayer:aLayer];
[aLayer release];
}
return _stylesLayer;
}
And in MyViewController I implemented the delegate method that does the drawing:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
// custom drawing code here
}
Now when a certain property changes I update the layer from MyViewController, which implicitly does a cross fade animation.
-(void)setStyles:(NSArray *)styles
{
[styles retain];
[_styles release];
_styles = nil;
_styles = styles;
[self.myView.stylesLayer setNeedsDisplay];
}

UIBarButtonItem frame? [duplicate]

UIBarButtonItem does not extend UIView, so there is nothing like a frame property.
But is there any way I can get what is it's CGRect frame, relative to the application UIWindow?
Do you like to use private APIs? If yes,
UIView* view = thatItem.view;
return [view convertRect:view.bounds toView:nil];
Of course no one wants this when targeting the AppStore. A more unreliable method, and also uses undocumented features, but will pass Apple's test, is to loop through the subviews to look for the corresponding button item.
NSMutableArray* buttons = [[NSMutableArray alloc] init];
for (UIControl* btn in theToolbarOrNavbar.subviews)
if ([btn isKindOfClass:[UIControl class]])
[buttons addObject:btn];
UIView* view = [buttons objectAtIndex:index];
[buttons release];
return [view convertRect:view.bounds toView:nil];
The index is the index to your bar item in the array of .items, after removing all blank items. This assumes the buttons are arranged in increasing order, which may not be. A more reliable method is to sort the buttons array in increasing .origin.x value. Of course this still assumes the bar button item must inherit the UIControl class, and are direct subviews of the toolbar/nav-bar, which again may not be.
As you can see, there are a lot of uncertainty when dealing with undocumented features. However, you just want to pop up something under the finger right? The UIBarButtonItem's .action can be a selector of the form:
-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event;
note the event argument — you can obtain the position of touch with
[[event.allTouches anyObject] locationInView:theWindow]
or the button view with
[[event.allTouches anyObject] view]
Therefore, there's no need to iterate the subviews or use undocumented features for what you want to do.
I didn't see this option posted (which in my opinion is much simpler), so here it is:
UIView *barButtonView = [barButtonItem valueForKey:#"view"];
In iOS 3.2, there's a much easier way to show an Action Sheet popover from a toolbar button. Merely do something like this:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIActionSheet *popupSheet;
// Prepare your action sheet
[popupSheet showFromBarButtonItem:sender animated:YES];
}
This is the implementation I use for my WEPopover project: (https://github.com/werner77/WEPopover):
#implementation UIBarButtonItem(WEPopover)
- (CGRect)frameInView:(UIView *)v {
UIView *theView = self.customView;
if (!theView.superview && [self respondsToSelector:#selector(view)]) {
theView = [self performSelector:#selector(view)];
}
UIView *parentView = theView.superview;
NSArray *subviews = parentView.subviews;
NSUInteger indexOfView = [subviews indexOfObject:theView];
NSUInteger subviewCount = subviews.count;
if (subviewCount > 0 && indexOfView != NSNotFound) {
UIView *button = [parentView.subviews objectAtIndex:indexOfView];
return [button convertRect:button.bounds toView:v];
} else {
return CGRectZero;
}
}
#end
As long as UIBarButtonItem (and UITabBarItem) does not inherit from UIView—for historical reasons UIBarItem inherits from NSObject—this craziness continues (as of this writing, iOS 8.2 and counting ... )
The best answer in this thread is obviously #KennyTM's. Don't be silly and use the private API to find the view.
Here's a oneline Swift solution to get an origin.x sorted array (like Kenny's answer suggests):
let buttonFrames = myToolbar.subviews.filter({
$0 is UIControl
}).sorted({
$0.frame.origin.x < $1.frame.origin.x
}).map({
$0.convertRect($0.bounds, toView:nil)
})
The array is now origin.x sorted with the UIBarButtonItem frames.
(If you feel the need to read more about other people's struggles with UIBarButtonItem, I recommend Ash Furrow's blog post from 2012: Exploring UIBarButtonItem)
I was able to get Werner Altewischer's WEpopover to work by passing up the toolbar along with the
UIBarButton:
Mod is in WEPopoverController.m
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item toolBar:(UIToolbar *)toolBar
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
animated:(BOOL)animated
{
self.currentUIControl = nil;
self.currentView = nil;
self.currentBarButtonItem = item;
self.currentArrowDirections = arrowDirections;
self.currentToolBar = toolBar;
UIView *v = [self keyView];
UIButton *button = nil;
for (UIView *subview in toolBar.subviews)
{
if ([[subview class].description isEqualToString:#"UIToolbarButton"])
{
for (id target in [(UIButton *)subview allTargets])
{
if (target == item)
{
button = (UIButton *)subview;
break;
}
}
if (button != nil) break;
}
}
CGRect rect = [button.superview convertRect:button.frame toView:v];
[self presentPopoverFromRect:rect inView:v permittedArrowDirections:arrowDirections animated:animated];
}
-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
UIView *view = [item valueForKey:#"view"];
return [view frame];
}
You can get it from the UINavigationBar view. The navigationBar is a UIView which has 2 or 3 custom subviews for the parts on the bar.
If you know that the UIBarButtonItem is currently shown in the navbar on the right, you can get its frame from navbar's subviews array.
First you need the navigationBar which you can get from the navigationController which you can get from the UIViewController. Then find the right most subview:
UINavigationBar* navbar = curViewController.navigationController.navigationBar;
UIView* rightView = nil;
for (UIView* v in navbar.subviews) {
if (rightView==nil) {
rightView = v;
} else if (v.frame.origin.x > rightView.frame.origin.x) {
rightView = v; // this view is further right
}
}
// at this point rightView contains the right most subview of the navbar
I haven't compiled this code so YMMV.
This is not the best solution and from some point of view it's not right solution and we can't do like follow because we access to object inside UIBarBattonItem implicitly, but you can try to do something like:
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[button setImage:[UIImage imageNamed:#"Menu_Icon"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(didPressitem) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.rightBarButtonItem = item;
CGPoint point = [self.view convertPoint:button.center fromView:(UIView *)self.navigationItem.rightBarButtonItem];
//this is like view because we use UIButton like "base" obj for
//UIBarButtonItem, but u should note that UIBarButtonItem base class
//is NSObject class not UIView class, for hiding warning we implicity
//cast UIBarButtonItem created with UIButton to UIView
NSLog(#"point %#", NSStringFromCGPoint(point));
as result i got next:
point {289, 22}
Before implement this code, be sure to call [window makeKeyAndVisible] in your Applition delegate application:didFinishLaunchingWithOptions: method!
- (void) someMethod
{
CGRect rect = [barButtonItem convertRect:barButtonItem.customview.bounds toView:[self keyView]];
}
- (UIView *)keyView {
UIWindow *w = [[UIApplication sharedApplication] keyWindow];
if (w.subviews.count > 0) {
return [w.subviews objectAtIndex:0];
} else {
return w;
}
}
I handled it as follows:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIView* view = [sender valueForKey:#"view"]; //use KVO to return the view
CGRect rect = [view convertRect:view.bounds toView:self.view];
//do stuff with the rect
}

ipad: predictive search in a popover

I want to implement this
1) when user start typing in a textfield a popOver flashes and shows the list of items in a table view in the popover as per the string entered in textfield.
2) Moreover this data should be refreshed with every new letter entered.
kind of predictive search.
Please help me with this and suggest possible ways to implement this.
UISearchDisplayController does most of the heavy lifting for you.
Place a UISearchBar (not a UITextField) in your view, and wire up a UISearchDisplayController to it.
// ProductViewController.h
#property IBOutlet UISearchBar *searchBar;
#property ProductSearchController *searchController;
// ProductViewController.m
- (void) viewDidLoad
{
[super viewDidLoad];
searchBar.placeholder = #"Search products";
searchBar.showsCancelButton = YES;
self.searchController = [[[ProductSearchController alloc]
initWithSearchBar:searchBar
contentsController:self] autorelease];
}
I usually subclass UISearchDisplayController and have it be it's own delegate, searchResultsDataSource and searchResultsDelegate. The latter two manage the result table in the normal manner.
// ProductSearchController.h
#interface ProductSearchController : UISearchDisplayController
<UISearchDisplayDelegate, UITableViewDelegate, UITableViewDataSource>
// ProductSearchController.m
- (id)initWithSearchBar:(UISearchBar *)searchBar
contentsController:(UIViewController *)viewController
{
self = [super initWithSearchBar:searchBar contentsController:viewController];
self.contents = [[NSMutableArray new] autorelease];
self.delegate = self;
self.searchResultsDataSource = self;
self.searchResultsDelegate = self;
return self;
}
Each keypress in the searchbar calls searchDisplayController:shouldReloadTableForSearchString:. A quick search can be implemented directly here.
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
// perform search and update self.contents (on main thread)
return YES;
}
If your search might take some time, do it in the background with NSOperationQueue. In my example, ProductSearchOperation will call showSearchResult: when and if it completes.
// ProductSearchController.h
#property INSOperationQueue *searchQueue;
// ProductSearchController.m
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
if (!searchQueue) {
self.searchQueue = [[NSOperationQueue new] autorelease];
searchQueue.maxConcurrentOperationCount = 1;
}
[searchQueue cancelAllOperations];
NSInvocationOperation *op = [[[ProductSearchOperation alloc]
initWithController:self
searchTerm:searchString] autorelease];
[searchQueue addOperation:op];
return NO;
}
- (void) showSearchResult:(NSMutableArray*)result
{
self.contents = result;
[self.searchResultsTableView
performSelectorOnMainThread:#selector(reloadData)
withObject:nil waitUntilDone:NO];
}
It sounds like you have a pretty good idea of an implementation already. My suggestion would be to present a UITableView in a popover with the search bar at the top, then simply drive the table view's data source using the search term and call reloadData on the table view every time the user types into the box.

Resources