How do I programmatically change text attributes in a WebView? - objective-c

I need to highlight certain segments of text within a WebView. Until now, I've been doing this by injecting javascript into the webview using [webView stringByEvaluatingJavaScriptFromString:jscript]. The javascript adds a tag to the text in which I change the background color to yellow. This usually works but sometimes I corrupt the html when the tag is inserted. I would rather use the - (void)changeAttributes:(id)sender method on WebView, which, as I understand, will apply the style in a way that won't break the underlying html. To do this I first call
[webView searchFor:aString direction:YES caseSensitive:NO wrap:YES];
To set the selection in the webview. Then I call
[webView changeAttributes:self];
When I send this message to my WebView instance, it invokes this method:
- (NSDictionary *)convertAttributes:(NSDictionary *)attributes {
NSMutableDictionary *newAttrs = [[NSMutableDictionary alloc] initWithDictionary:attributes];
NSColor *background = [NSColor yellowColor];
[newAttrs setValue:background forKey:NSBackgroundColorAttributeName];
return newAttrs;
}
Which should change the background color of the selected text to yellow, but it does not. Do standard Cocoa text attributes apply to a webview? Is this method intended to be used for this purpose, or should I go back to javascript?

These attributes are those for NSFonts. As described in this document, the sequence of events is usually this:
[text/webView changeAttributes:obj] is performed. Usually it's done by obj itself.
text/webView passes the font attributes to obj using [obj convertAttributes:...].
obj returns the attributes as it wants.
text/webView sets the font attributes accordingly.
Anyway I won't recommend this approach. I assume you're on OS X, not on iOS. Then, I would suggest you to get the DOM elements using WebFrame's DOMDocument and set the styles directly.

Related

Click on UILabel and perform action

I have a simple problem, I have a string like "#my#name#is#umesh#verma" and assign to a UITableview cell label,
cell.detaillabel.text = #"#my#name#is#umesh#verma";
My Problem is how to get each word name when I click on single item.
If I click on #umesh then I get "umesh" word..
More better solution is add custom label which supports touches. For example TTTAttributedLabel supports touches on links.
Main task is get notification when user touch a word and to identify the word.
You can add URLs (with special format) to any word and subscribe to a notification when user click it (as delegate to the label). For example you can create this URL for "word":
touchscheme://word
I haven't checked about how to perform an action when clicking on a UILabel. However, I experienced that with UITextView. You can use "NSLinkAttributeName" to do that.
Basically, from your original string, try to find the range of string that you need to trigger actions. Then add a link value.
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:yourString];
[attributedString addAttribute:NSLinkAttributeName value:url range:range];
[textView setAttributedText:attributedString];
Set delegate to your textView and handle the following method
-(BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange{
// You can retrive your string here or perform an action
return YES;
}
Hope this would be helpful for you.

NSStatusItem change image for dark tint

With OSX 10.10 beta 3, Apple released their dark tint option. Unfortunately, it also means that pretty much all status bar icons (with the exception of Apple's and Path Finder's that I've seen), including mine, remain dark on a dark background. How can I provide an alternate image for when dark tint is applied?
I don't see an API change on NSStatusBar or NSStatusItem that shows me a change, I'm assuming it's a notification or something reactive to easily make the change as the user alters the tint.
Current code to draw the image is encased within an NSView:
- (void)drawRect:(NSRect)dirtyRect
{
// set view background color
if (self.isActive) {
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);
// set image
NSImage *image = (self.isActive ? self.alternateImage : self.image);
_imageView.image = image;
}
TL;DR: You don't have to do anything special in Dark Theme. Give NSStatusItem (or NSStatusBarButton) a template image and it will style it correctly in any menubar context.
The reason why some apps' status items (such as PathFinder's) already work in Dark Theme is because they're not setting their own custom view on the StatusItem, but only setting a template image on the StatusItem.
Something like:
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:#"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];
This works exactly as you'd expect in Mavericks and earlier, as well as Yosemite and any future releases because it allows AppKit to do all of the styling of the image depending on the status item state.
Mavericks
In Mavericks (and earlier) there were only 2 unique styles of the items. Unpressed and Pressed. These two styles pretty much looked purely black and purely white, respectively. (Actually "purely black" isn't entirely correct -- there was a small effect that made them look slightly inset).
Because there were only two possible state, status bar apps could set their own view and easily get the same appearance by just drawing black or white depending on their highlighted state. (But again note that it wasn't purely black, so apps either had to build the effect in the image or be satisfied with a hardly-noticeable out of place icon).
Yosemite
In Yosemite there are at least 32 unique styling of items. Unpressed in Dark Theme is only one of those. There is no practical (or unpractical) way for an app to be able to do their own styling of items and have it look correct in all contexts.
Here are examples of six of those possible stylings:
Status items on an inactive menubar now have a specific styling, as opposed to a simple opacity change as in the past. Disabled appearance is one other possible variation; there are also other additional dimensions to this matrix of possibilities.
API
Arbitrary views set as NSStatusItem's view property have no way to capture all of these variations, hence it (and other related API) is deprecated in 10.10.
However, seed 3 introduces new API on NSStatusItem:
#property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);
This piece of API has a few purposes:
An app can now get the screen position (or show a popover from) a status item without setting its own custom view.
Removes the need for API like image, title, sendActionOn: on NSStatusItem.
Provides a class for new API: i.e. looksDisabled. This allows apps to get the standard disabled/off styling (like Bluetooth/Time Machine when off) without requiring a custom image.
If there's something that can't be done with the current (non- custom view) API, please file an enhancement request for it. StatusItems should provide behavior or appearances in a way that it standard across all status items.
More discussion is at https://devforums.apple.com/thread/234839, although I've summarized most everything here.
I end up did something like following to my custom drag and drop NSStatusItemView: (Using Swift)
var isDark = false
func isDarkMode() {
isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
isDarkMode()
// Now use "isDark" to determine the drawing colour.
if isDark {
// ...
} else {
// ...
}
}
When the user changed the Theme in System Preferences, the NSView will be called by the system for re-drawing, you can change the icon colour accordingly.
If you wish to adjust other custom UI outside this view, you can either use KVO to observer the isDark key of the view or do it on your own.
I created a basic wrapper around NSStatusItem that you can use to provide support for 10.10 and earlier with custom views in the status bar. You can find it here: https://github.com/noahsmartin/YosemiteMenuBar The basic idea is to draw the custom view into a NSImage and use this image as a template image for the status bar item. This wrapper also forwards click events to the custom view so they can be handled the same way as pre 10.10. The project contains a basic example of how YosemiteMenuBar can be used with a custom view on the status bar.
Newest swift code set image template method is here:
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
button.image?.isTemplate = true // Just add this line
button.action = #selector(togglePopover(_:))
}
Then it will change the image when dark mode.
When your application has drawn any GUI element you can get its appearance via [NSAppearance currentAppearance] which itself has a name property that holds something like
NSAppearanceNameVibrantDark->NSAppearanceNameAqua->NSAppearanceNameAquaMavericks
The first part is the appearance’s name, which is also available as a constant in NSAppearanceNameVibrantDark or NSAppearanceNameVibrantLight.
I don’t know if there’s a way to get just the first part, but I think this does the trick for now.
Example code:
-(void)awakeFromNib {
NSStatusItem* myStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
myStatusItem.title = #"Hello World";
if ([[[NSAppearance currentAppearance] name] containsString:NSAppearanceNameVibrantDark]) {
myStatusItem.title = #"Dark Interface";
} else {
myStatusItem.title = #"Light Interface";
}
}
But just in case you do want to monitor the status changes you can. I also know there is a better way to determine lite/dark mode than what's been said above, but I can remember it right now.
// Monitor menu/dock theme changes...
[[NSDistributedNotificationCenter defaultCenter] addObserver: self selector: #selector(themeChange:) name:#"AppleInterfaceThemeChangedNotification" object: NULL];
//
-(void) themeChange :(NSNotification *) notification
{
NSLog (#"%#", notification);
}

How to auto scroll a WebView after DOM changes in Cocoa / WebKit

I'm building a small Mac Application that gets continuously supplied with data via a web socket. After processing each data segment, the data is displayed in a WebView. New data never replaces any data in the WebView instead new data is always appended to the WebView's content using DOM manipulations. The code sample below should give you an idea of what I'm doing.
DOMDocument *doc = self.webview.mainFrame.DOMDocument;
DOMHTMLElement *container = (DOMHTMLElement *)[doc createElement:#"div"];
NSString *html = [NSString stringWithFormat:#"... omitted for the sake of brevity ... "];
[container setInnerHTML:html];
[doc.body appendChild:container];
The rendering of the WebView apparently happens asynchronously. Is there a way to tell when the DOM manipulation finished and the content has been drawn? I want to use something like [webview scrollToEndOfDocument:self] to implement auto scrolling. Listening for DOM Events didn't help since they seem to be triggered when the DOM was modified but before these changes have been rendered. The code I'm using so far is similar to the following
[self.webview.mainFrame.DOMDocument addEventListener:#"DOMSubtreeModified" listener:self useCapture:NO];
in conjunction with
- (void)handleEvent:(DOMEvent *)event
{
[self.webview scrollToEndOfDocument:self];
}
The problem with this code is that the scrolling happens too early. I'm basically always one data segment behind. Can I register for a callback / notification of any kind that is triggered when the content was drawn?
Using timers
Auto scrolling can be implemented using an NSTimer. The challenge this solution bears is to figure out when to disable the timer to allow manual scrolling. I wasn't able to do the latter. Anyways, here is the code that enables autoscrolling using a timer:
self.WebViewAutoScrollTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0/30.0
target:self
selector:#selector(scrollWebView:)
userInfo:nil
repeats:YES];
scrollWebView: simply being a method that calls scrollToEndOfDocument: on the web view.
Using notifications
Listing to NSViewFrameDidChangeNotification emitted by the web view allows to only scroll in the event of frame size changes. These changes occur when new content is added but also when the size of the encapsulating view changes, e.g. the window is resized. My implementation does not distinguish between those two scenarios.
NSClipView *contentView = self.webView.enclosingScrollView.contentView;
[contentView setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserverForName:NSViewFrameDidChangeNotification
object:contentView
queue:nil
usingBlock:^(NSNotification *notification) {
[self.webView scrollToEndOfDocument:self];
}];
Note: It is important that you instruct the content view of the web view's scroll view – think about this a couple of times and it will start to make sense – to post notifications when its frame size changes because NSView instances do not do this by default. This is accomplished using the first two lines of code in the example above.

setting an accessibilityLabel on a UIImageView contained in UITableView header

I have a UITableView that I build in loadView. One of the things I do in loadView is create a UIView to act as the table header and stuff a UIImageView into it. The image view contains an image that is a stylized title, so I want to add an accessibility label for VoiceOver users. However, I can't get VoiceOver to "focus" on the image in order to read the label, and the Accessibility Inspector doesn't respond to clicking on the image in the simulator. My (abbreviated) code follows:
... in -loadView ...
// Make header view
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(...)];
UIImageView *titleImageView = [[UIImageView alloc] initWithImage:[self titleImage]];
titleImageView.accessibilityLabel = [self accessibilityLabelForTitleImage];
[headerView addSubview:titleImageView];
// Make table view
self.tableView = [[UITableView alloc] initWithFrame:CGRect(...) style:UITableViewStylePlain];
self.tableView.tableHeaderView = headerView;
... code continues ...
I've stepped through in gdb and accessibilityLabelForTitleImage returns a string. po [titleImageView accessibilityLabel] prints out the correct string, but I'm still unable to focus on the image view. Note that the views themselves appear and respond as appropriate.
Am I missing something? Is there a way to force VoiceOver to acknowledge an image view?
In Voice-Over , in order to make an element accessible :-
you have to set setIsAccessibilityElement property as true which i don't find in your code.
The other important point is that to make child elements (subviews) to be accessible , you have to seperately make them accessible while the parent should not be accessible(you have to specify this also).
Implement the UIAccessibilityContainer Protocol in your custom - cell.
It will be a big story if i go on .Please refer this Accessibility voice over by apple.
Hope this helps.
I used KIF for testing my IOS app. In my tableview, I assigned value to tableview.accesssibilityIdentifier instead of tableview.accessibilityLabel. It worked for me. Wanna give it a try?
Voice-Over sometimes can get nasty and just by setting isAccessibilityElement might not work.
In this case try setting accessibilityElements on the parent view and include the child views in the array, like this:
parentView.accessibilityElements = [childView1, childView1, childView1]
Doing it also ensures that the accessibility items are being read in the order you want.

How to generate grid in PDF using Cocoa?

I'm a beginner to Cocoa and Objective-C.
I want to make a Cocoa application that will generate a grid of boxes (used for practicing Chinese calligraphy) to export as a PDF, similar to this online generator: http://incompetech.com/graphpaper/chinesequarter/.
How should I generate the grid? I've tried to use Quartz with a CustomView, but didn't manage to get very far. Also, once the grid is drawn in the CustomView, what is the method for "printing" that to a PDF?
Thanks for the help.
How should I generate the grid?
Implement a custom view that draws it.
I've tried to use Quartz with a CustomView, …
That's one way; AppKit drawing is the other. Most parts of them are very similar, though; AppKit is directly based on PostScript, while Quartz is indirectly based on PostScript.
… but didn't manage to get very far.
You should ask a more specific question about your problem.
Also, once the grid is drawn in the CustomView, what is the method for "printing" that to a PDF?
Send it a dataWithPDFInsideRect: message, passing its bounds.
Note that there is no “once the grid is drawn in the CustomView”. Though there may be some internal caching, conceptually, a view does not draw once and hold onto it; it draws when needed, every time it's needed, into where it's needed. When the window needs to be redrawn, Cocoa will tell any views that are in the dirty area to (re)draw, and they will draw ultimately to the screen. When you ask for PDF data, that will also tell the view to draw, and it will draw into a context that records PDF data. This allows the view both to be lazy (draw only when needed) and to draw differently in different contexts (e.g., when printing).
Oops, you were asking about Cocoa and this is Cocoa Touch, but I'll leave it here as it may be some use (at least to others who find this later).
You can draw things in the view and then put what's there into a pdf.
This code will take what's drawn in a UIView (called sheetView here), put it into a pdf, then put that as an attachment in an email (so you can see it for now). You'll need to reference the protocol MFMailComposeViewControllerDelegate in your header.
if ([MFMailComposeViewController canSendMail]) {
//set up PDF rendering context
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, sheetView.bounds, nil);
UIGraphicsBeginPDFPage();
//tell our view to draw (would normally use setNeedsDisplay, but need drawn now).
[sheetView drawRect:sheetView.bounds];
//remove PDF rendering context
UIGraphicsEndPDFContext();
//send PDF data in mail message as an attachment
MFMailComposeViewController *mailComposer = [[[MFMailComposeViewController alloc] init] autorelease];
mailComposer.mailComposeDelegate = self;If
[mailComposer addAttachmentData:pdfData mimeType:#"application/pdf" fileName:#"SheetView.pdf"];
[self presentModalViewController:mailComposer animated:YES];
}
else {
if (WARNINGS) NSLog(#"Device is unable to send email in its current state.");
}
You'll also need this method...
#pragma mark -
#pragma mark MFMailComposeViewControllerDelegate protocol method
//also need to implement the following method, so that the email composer can let
//us know that the user has clicked either Send or Cancel in the window.
//It's our duty to end the modal session here.
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
[self dismissModalViewControllerAnimated:YES];
}