I have a mac cocoa app with a webview that contains some text. I would like to search through that text using the default find bar provided by NSTextFinder. As easy as this may seem reading through the NSTextFinder class reference, I cannot get the find bar to show up. What am I missing?
As a sidenote:
- Yes, I tried setting findBarContainer to a different view, same thing. I reverted back to the scroll view to eliminate complexity in debugging
- performTextFinderAction is called to perform the find operation
**App Delegate:**
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.textFinderController = [[NSTextFinder alloc] init];
self.webView = [[STEWebView alloc] initWithFrame:CGRectMake(0, 0, self.window.frame.size.width, 200)];
[[self.window contentView] addSubview:self.webView];
[self.textFinderController setClient:self.webView];
[self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
[[self.webView mainFrame] loadHTMLString:#"sample string" baseURL:NULL];
}
- (IBAction)performTextFinderAction:(id)sender {
[self.textFinderController performAction:[sender tag]];
}
**STEWebView**
#interface STEWebView : WebView <NSTextFinderClient>
#end
#implementation STEWebView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (NSUInteger) stringLength {
return [[self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"] length];
}
- (NSString *)string {
return [self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"];
}
In my tests, WebView.enclosingScrollView was null.
// [self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
NSLog(#"%#", self.webView.enclosingScrollView);
Using the following category on NSView, it is possible to find the nested subview that extends NSScrollView, and set that as the container, allowing the NSTextFinder to display beautifully within a WebView
#interface NSView (ScrollView)
- (NSScrollView *) scrollView;
#end
#implementation NSView (ScrollView)
- (NSScrollView *) scrollView {
if ([self isKindOfClass:[NSScrollView class]]) {
return (NSScrollView *)self;
}
if ([self.subviews count] == 0) {
return nil;
}
for (NSView *subview in self.subviews) {
NSView *scrollView = [subview scrollView];
if (scrollView != nil) {
return (NSScrollView *)scrollView;
}
}
return nil;
}
#end
And in your applicationDidFinishLaunching:aNotification:
[self.textFinderController setFindBarContainer:[self scrollView]];
To get the Find Bar to appear (as opposed to the default Find Panel), you simply have to use the setUsesFindBar: method.
In your case, you'll want to do (in your applicationDidFinishLaunching:aNotification method):
[textFinderController setUsesFindBar:YES];
//Optionally, incremental searching is a nice feature
[textFinderController setIncrementalSearchingEnabled:YES];
Finally got this to show up.
First set your NSTextFinder instances' client to a class implementing the <NSTextFinderClient> protocol:
self.textFinder.client = self.textFinderController;
Next, make sure your NSTextFinder has a findBarContainer set to the webView category described by Michael Robinson, or get the scrollview within the webView yourself:
self.textFinder.findBarContainer = [self.webView scrollView];
Set the find bar position above the content (or wherever you wish):
[self.webView scrollView].findBarPosition = NSScrollViewFindBarPositionAboveContent;
Finally, tell it to show up:
[self.textFinder performAction:NSTextFinderActionShowFindInterface];
It should show up in your webView:
Also, not sure if it makes a difference, but I have the NSTextFinder in the XIB, with a referencing outlet:
#property (strong) IBOutlet NSTextFinder *textFinder;
You may also be able to get it by simply initing it like normal: self.textFinder = [[NSTextFinder alloc] init];
Related
I'm trying to understand how best to impliment drag and drop of files from the Finder to a NSTableView which will subsequently list those files.
I've built a little test application as a proving ground.
Currently I have a single NSTableView with FileListController as it's datasourse. It's basically a NSMutableArray of File objects.
I'm trying to work out the best / right way to impliment the drag and drop code for the NSTableView.
My first approach was to subclass the NSTableView and impliment the required methods :
TableViewDropper.h
#import <Cocoa/Cocoa.h>
#interface TableViewDropper : NSTableView
#end
TableViewDropper.m
#import "TableViewDropper.h"
#implementation TableViewDropper {
BOOL highlight;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code here.
NSLog(#"init in initWithCoder in TableViewDropper.h");
[self registerForDraggedTypes:#[NSFilenamesPboardType]];
}
return self;
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
NSLog(#"performDragOperation in TableViewDropper.h");
return YES;
}
- (BOOL)prepareForDragOperation:(id)sender {
NSLog(#"prepareForDragOperation called in TableViewDropper.h");
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"%#",filenames);
return YES;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
highlight=YES;
[self setNeedsDisplay: YES];
NSLog(#"drag entered in TableViewDropper.h");
return NSDragOperationCopy;
}
- (void)draggingExited:(id)sender
{
highlight=NO;
[self setNeedsDisplay: YES];
NSLog(#"drag exit in TableViewDropper.h");
}
-(void)drawRect:(NSRect)rect
{
[super drawRect:rect];
if ( highlight ) {
//highlight by overlaying a gray border
[[NSColor greenColor] set];
[NSBezierPath setDefaultLineWidth: 18];
[NSBezierPath strokeRect: rect];
}
}
#end
The draggingEntered and draggingExited methods both get called but prepareForDragOperation and performDragOperation don't. I don't understand why not?
Next I thought I'll subclass the ClipView of the NSTableView instead. So using the same code as above and just chaging the class type in the header file to NSClipView I find that prepareForDragOperation and performDragOperation now work as expected, however the ClipView doesn't highlight.
If I subclass the NSScrollView then all the methods get called and the highlighting works but not as required. It's very thin and as expected round the entire NSTableView and not just the bit below the table header as I'd like.
So my question is what is the right thing to sublclass and what methods do I need so that when I peform a drag and drop from the Finder, the ClipView highlights properly and prepareForDragOperation and performDragOperation get called.
And also when performDragOperation is successful how can this method call a method within my FileListController telling it to create a new File object and adding it to the NSMutableArray?
Answering my own question.
It seems that subclassing the NSTableView (not the NSScrollView or the NSClipView) is the right way to go.
Including this method in the subclass :
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
return [self draggingEntered:sender];
}
Solves the problem of prepareForDragOperation and performDragOperation not being called.
To allow you to call a method within a controller class, you make the delagate of your NSTextView to be the controller. In this case FileListController.
Then within performDragOperation in the NSTableView subclass you use something like :
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
id delegate = [self delegate];
if ([delegate respondsToSelector:#selector(doSomething:)]) {
[delegate performSelector:#selector(doSomething:)
withObject:filenames];
}
This will call the doSomething method in the controller object.
Updated example project code here.
I'm kinda new into bindings, somehow prevented it. But I want to use them now.
Talking about OSX and this is programmed in code not in IB.
So, I have data coming from CoreData into my ArrayController. A NSCollectionView is bound to this arraycontroller and if there is data, this binding works the data is displayed.
But, each item has some buttons, sliders, textfields. On a click, a code will change the tag or value of those things. I thought it is enough when I send the change to coredata and save it. Shouldnt the arraycontroller get this and update my items in the collectionview?
Because the tags(first thing I tried) dont get updated if its updated in coredata.
Do those fields have to be somehow bound?
the tag is set in a subclass of NSCollectionViewItem this way:
[[(BEItem *)[self view] valueSlider] setTag:[[representedObject itemTag] intValue]];
Is there anything I have to tell the CollectionView to update itself and take the new data from the controller?
Thanks
Benjamin
EDIT
I have changed my collectionview. I read it isnt really possible to bind a representable object and in the answer below, it is bound to some property, but this property isnt updated either. Then I read about newItemForRepresentedObject that you should use this function. Now, I created everything like shown below, but the program is always crashing after 10 seconds or something and nothing is displayed. It is continuously calling setChannelID, but never setting the ID to the property. Because of that it is always called I think this is the problem. (The if gets never to only return)
What is the problem here? Im really confused by the collectionview by now.
And this is just code, nothing in IB.
Setting up the View in appdelegate:
NSCollectionViewItem *testitem = [[NSCollectionViewItem alloc] init];
[testitem setView:[ChannelView new]];
self.collectionView = [[ChannelCollectionView alloc] initWithFrame:NSMakeRect(10, 0, mixerWidth, self.splitview.frame.size.height)]; // initWithFrame:[[[self window] contentView] frame]
[self.collectionView setItemPrototype:testitem];
[self.collectionView setMaxNumberOfRows:1];
[self.collectionView setAutoresizingMask:(NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin | NSViewMinYMargin | NSViewHeightSizable| NSViewMaxYMargin)];
[self.collectionView setAutoresizesSubviews:YES];
[self.collectionView bind:NSContentBinding toObject:self.channelController withKeyPath:#"arrangedObjects" options:nil];
ChannelView:
#import <Cocoa/Cocoa.h>
#interface ChannelView : NSView
#property (readwrite, nonatomic, copy) NSString *channelName;
#property (readwrite, nonatomic, copy) NSNumber *channelID;
#property (readwrite) NSTextField *channelNameField;
#property (readwrite) NSTextField *deviceChannelField;
#end
#implementation ChannelView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:NSMakeRect(0, 0, 300, 500)];
if (self) {
// Initialization code here.
ColorView *test = [[ColorView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
self.channelNameField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)];
self.deviceChannelField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 50, 100, 20)];
[self addSubview:test];
[self addSubview:self.channelNameField];
[self addSubview:self.deviceChannelField];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
//add die teile
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
// setters.
-(void)setChannelID:(NSNumber *)chanID
{
//NSLog(#"hallo");
if (self.channelID == chanID) {
return;
NSLog(#"da");
}
else {
NSLog(#"hello"); //just this in debug output
self.channelID = [chanID copy];
NSLog(#"no output");
// self.channelID = chanID;
NSLog(#"chanid %d current: %d", chanID.intValue, self.channelID.intValue); //never shown in debug
[self.deviceChannelField setStringValue:[NSString stringWithFormat:#"%d",self.channelID.intValue]];
}
}
#end
And this piece in my subclasses NSCollectionView
- (NSCollectionViewItem *)newItemForRepresentedObject:(ChannelsToMixes*)object
{
NSCollectionViewItem *item = [super newItemForRepresentedObject:object];
// ChannelView *view = (ChannelView *)[item view];
NSLog(#"cahnnelid: %d",object.channelID.intValue);
// [view bind:#"title" toObject:object withKeyPath:#"title" options:nil];
[item.view bind:#"channelID" toObject:object withKeyPath:#"channelID" options:nil];
//NSLog(#"test");
//NSLog(#"%#",object);
return item;
}
If anyone knows why the setter isnt setting the property give me a tip :)
It should be able to do this and is not released or anything, at least that I know of (using ARC)
Yes, you have to bind the value of your slider to your CollectionViewItem.
You can either do this in code with this method:
-bind:toObject:withKeyPath:options:
Which would look in your example like this:
[[(BEItem *)[self view] valueSlider] bind:#"tag" toObject:self withKeyPath:#"itemTag" options:nil];
Or, if you use IB, in the InterfaceBuilder by setting the value to bind to your Colletion View Item representedObject.itemTag
Currently we are using this block of code that customizes the UINavigationBar background image throughout our app:
#implementation UINavigationBar(MyExtensions)
- (UIImage *)barBackground {
return [UIImage imageNamed:#"GlobalTitleBackground.png"];
}
- (void)didMoveToSuperview {
//iOS5 only
if ([self respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)])
{
[self setBackgroundImage:[self barBackground] forBarMetrics:UIBarMetricsDefault];
}
}
//this doesn't work on iOS5 but is needed for iOS4 and earlier
- (void)drawRect:(CGRect)rect {
//draw image
[[self barBackground] drawInRect:rect];
}
#end
In general, this works great. The problem I'm encountering is that when I create a MFMailComposeViewController, it's background also gets customized.
Therefore, is it possible, given the code that I have right now, to do the customization on all UINavigationBars except the UINavigationBar created by the MFMailComposeViewController?
Thanks in advance!
One solution would be to filter out that specific navigation controller by using the view.tag property.
When you create your MFMailComposeViewController to add a tag to the nav bar.
For example:
//In other VC
MFMailController *mailVC = [[MFMailController alloc] init];
mailVC.navigationBar.tag = 5678;
//In #implementation UINavigationBar(MyExtensions)
- (UIImage *)barBackground {
if (self.tag != 5678)
return [UIImage imageNamed:#"GlobalTitleBackground.png"];
}
return nil;
}
I'm writing an iOS 5 app (in Xcode 4.3, using Storyboards and ARC) that has some table cells that need to respond to horizontal pans. I had a table setup that worked really well but then I needed to implement the same behavior on another scene. I figured the best-practices way would be to abstract out the gesture-recognizing and -handling code into subclasses. But now the tableView won't scroll, and the solution I had for this problem under the old method doesn't help.
I have a RestaurantViewController which inherits from UIViewController and has a property ULPanningTableView *tableView. Some of the table's cells are MenuItemCells and inherit from ULPanningTableViewCell. The table's delegate and data source are the RestaurantViewController.
ULPanningTableViewCell inherits from UITableViewCell and is pretty close to the original, the only difference being that it has properties to keep track of the cell's front and back views, and the custom backgrounds.
ULPanningTableView is a bit more complicated, since it has to set up the recognition and handling.
ULPanningTableView.h:
#import <UIKit/UIKit.h>
#interface ULPanningTableView : UITableView <UIGestureRecognizerDelegate>
#property (nonatomic) float openCellLastTX;
#property (nonatomic, strong) NSIndexPath *openCellIndexPath;
- (id)dequeueReusablePanningCellWithIdentifier:(NSString *)identifier;
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer;
// ... some helpers for handlePan:
#end
and ULPanningTableView.m:
#import "ULPanningTableView.h"
#import "ULPanningTableViewCell.h"
#implementation ULPanningTableView
#synthesize openCellIndexPath=_openCellIndexPath, openCellLastTX=_openCellLastTX;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#pragma mark - Table View Helpers
- (id)dequeueReusablePanningCellWithIdentifier:(NSString *)identifier
{
ULPanningTableViewCell *cell = (ULPanningTableViewCell *)[self dequeueReusableCellWithIdentifier:identifier];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[panGestureRecognizer setDelegate:self];
[cell addGestureRecognizer:panGestureRecognizer];
return cell;
}
#pragma mark - UIGestureRecognizerDelegate protocol
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// for testing: only allow UIScrollViewPanGestureRecognizers to begin
NSString *gr = NSStringFromClass([gestureRecognizer class]);
if ([gr isEqualToString:#"UIScrollViewPanGestureRecognizer"]) {
return YES;
} else {
return NO;
}
}
#pragma mark - panhandling
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer
{
// ...
}
// ... some helpers for handlePan:
#end
I've played around with gestureRecognizerShouldBegin:, because that was how I solved this problem back when these weren't separate classes (ULPanningTableView stuff was implemented inside RestaurantViewController and ULPanningTableViewCell was stuff was implemented in MenuItemCell. I would essentially return NO for gestures where the translationInView was more vertical than horizontal). Anyway, I can't get the table to scroll! I can get the pan gestures to be recognized if I return YES from gestureRecognizerShouldBegin:, or if I remove the UIGestureRecognizerDelegate implementation entirely.
I'm still a beginner in iOS, and in Objective-C, so I only have hunches based on things I've read, and I'm under the impression from a similar problem that the culprit is UIScrollViewPanGestureRecognizer doing voodoo with the responder chain...
I would greatly appreciate any light you can shed on this!
Ok, so I feel really silly. -handlePan: is already a method! I changed it to -handleCustomPan: and it will handle other pans normally. I'm not sure why it wasn't crashing, but there it is. Oh, and I had to keep the UIScrollViewPanGestureRecognizer edge case in -gestureRecognizerDidBegin::
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
NSString *gr = NSStringFromClass([gestureRecognizer class]);
if ([gr isEqualToString:#"UIScrollViewPanGestureRecognizer"]) {
// allow scroll to begin
return YES;
} else if ([gr isEqualToString:#"UIPanGestureRecognizer"]){
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *)gestureRecognizer;
// allow horizontal pans to begin
ULPanningTableViewCell *cell = (ULPanningTableViewCell *)[panGR view];
CGPoint translation = [panGR translationInView:[cell superview]];
BOOL should = (fabs(translation.x) / fabs(translation.y) > 1) ? YES : NO;
if (!should) {
[self closeOpenCellAnimated:YES];
}
return should;
} else {
NSLog(#"%#",gestureRecognizer);
return NO;
}
}
The setup:
My application has background views that are set into full screen mode using enterFullScreenMode:withOptions:.
To these background views I move a content view as a subview using removeFromSuperview and addSubview:.
Further, there is a preferences view that I add to the content view as described before.
The preferences view comes from a XIB and contains NSControls like checkboxes, circular slides, combo box, ...
The whole setup is much more complicated which makes it very difficult to post code here. Nevertheless, I will add some parts if you request them.
The problem:
When I select any of the controls on the preferences view the elements flicker. That means the checkbox for example disappears and reappears.
I already tried to solve the problem running setNeedsDisplay:, setNeedsLayout or makeKeyAndOrderFront: in the viewDidMoveToSuperview method of the view. Though, nothing helped. What can I do?
Edit 1:
I took a screen capture of the application so you can see what happens.
Edit 2:
Here is the PreferencesViewController.h:
// PreferencesViewController.h
#import <Cocoa/Cocoa.h>
#import "SlideShowModelProtocol.h"
#import "DisplayInfoController.h"
#interface PreferencesViewController : NSViewController {
id<SlideShowModelProtocol> m_localModel;
DisplayInfoController* m_displayInfoController;
}
#property (readonly) id<SlideShowModelProtocol> localModel;
#property (readwrite, assign) IBOutlet DisplayInfoController* displayInfoController;
- (void)moveViewToSuperview:(NSView*)superview;
- (void)showOnView:(NSView*)superview;
- (void)removeViewFromSuperview;
- (BOOL)viewHasSuperview;
- (void)updateModelSettings;
- (IBAction)cancelView:(id)sender;
- (IBAction)confirmView:(id)sender;
#end
... and the implementation PreferencesViewController.m:
// PreferencesViewController.m
#import "PreferencesViewController.h"
#import "ApplicationController.h"
#import "DisplayInfo.h"
#interface PreferencesViewController()
- (void)centerViewOnSuperview;
- (void)loadModel;
#end
#implementation PreferencesViewController
- (id)init {
self = [super initWithNibName:#"PreferencesView" bundle:nil];
if (self != nil) {
m_localModel = nil;
m_displayInfoController = nil; // Assigned in Interface Builder.
}
return self;
}
#synthesize localModel = m_localModel;
#synthesize displayInfoController = m_displayInfoController;
- (void)loadModel {
[self willChangeValueForKey:#"localModel"];
// Retrieve deep copy of the model.
m_localModel = [[[ApplicationController sharedController] model] copyWithZone:nil];
// Reset the table view selection as saved in the model.
NSIndexSet* selectionIndices = [NSIndexSet indexSetWithIndex:[[m_localModel selectedScreenIndex] unsignedIntegerValue]];
[[m_displayInfoController displayInfoTableView] selectRowIndexes:selectionIndices byExtendingSelection:NO];
[self didChangeValueForKey:#"localModel"];
}
/**
Moves and positions the view on the given superview (aka another screen).
#param superview A superview.
*/
- (void)showOnView:(NSView*)superview {
[self moveViewToSuperview:superview];
[self centerViewOnSuperview];
}
/**
Moves the view on the given superview.
#param superview A superview (In full screen mode a background view).
*/
- (void)moveViewToSuperview:(NSView*)superview {
if ([[self view] superview] == superview) {
return;
}
[self loadModel];
[[self view] removeFromSuperview];
[superview addSubview:[self view]];
}
- (void)removeViewFromSuperview {
[[self view] removeFromSuperview];
}
- (void)centerViewOnSuperview {
NSRect superviewFrame = self.view.superview.frame;
NSRect viewFrame = self.view.frame;
float viewFrameWidth = viewFrame.size.width;
float viewFrameHeight = viewFrame.size.height;
float xPos = 0.5f * superviewFrame.size.width - 0.5f * viewFrameWidth;
float yPos = 0.5f * superviewFrame.size.height - 0.5f * viewFrameHeight;
NSRect frame = NSMakeRect(xPos, yPos, viewFrameWidth, viewFrameHeight);
[self.view setFrame:frame];
}
- (BOOL)viewHasSuperview {
return [[self view] superview] != nil;
}
- (void)updateModelSettings {
id<SlideShowModelProtocol> globalModel = [[ApplicationController sharedController] model];
[globalModel setFadeDuration:[m_localModel fadeDuration]];
[globalModel setStayDuration:[m_localModel stayDuration]];
[globalModel setStartWithFirst:[m_localModel startWithFirst]];
[globalModel setSortingMode:[m_localModel sortingMode]];
[globalModel setAnimationIsActive:[m_localModel animationIsActive]];
[globalModel setSelectedScreenIndex:[m_localModel selectedScreenIndex]];
[globalModel setPanAmount:[m_localModel panAmount]];
[globalModel setZoomAmount:[m_localModel zoomAmount]];
[globalModel setZoomFactor:[m_localModel zoomFactor]];
}
#pragma mark -
#pragma mark UserInterface
- (IBAction)cancelView:(id)sender {
[[ApplicationController sharedController] hidePreferencesViewModelSettingsUpdated:NO];
}
- (IBAction)confirmView:(id)sender {
ApplicationController* applicationController = [ApplicationController sharedController];
if ([[applicationController model] isEqualToModel:m_localModel]) {
[self cancelView:sender];
return;
}
[self updateModelSettings];
[applicationController hidePreferencesViewModelSettingsUpdated:YES];
}
#end
As already told in the comments to my post I created a new PreferencesViewTest.xib and added the GUI controls step by step. I also added the bindings, controllers and so on. Everything worked fine - no flickering. Then I assigned RETURN as the key equivalent for the OK button in Interface Builder. I started the application once again to check the behavior .... it flickers! I double checked the phenomenon with the former PreferencesView.xib by removing the key equivalent from the OK button .... it does not flicker! - Crazy shit. But why..?