macOS Apple Help Authoring - Anchors - macos

I'm trying to make an Apple Help book for my macOS app that I'm ready to release. However, I am trying to make anchors work in my HTML. By Apple's definition:
"Anchors allow you to uniquely identify topics in your help book. When
a user follows a link to an anchor, Help Viewer loads the page
containing the anchor. ... You can also use anchors to load an
anchored page from within your application by calling the the
NSHelpManager method openHelpAnchor:inBook: ..."
Example from Apple: <a name="ArrivalTimesUsingStopID"></a>
In my Apple, I have an NSAlert which has the following code to display the help button so that when you click on it, it opens the specified anchor string.
alert.showsHelp = true
alert.helpAnchor = NSHelpManager.AnchorName(stringLiteral: "ArrivalTimesUsingStopID")
Running the code does display the help button and Mac Help does open, but to an error saying that the specified content cannot be found. Not sure why the anchors aren't working because I can access the Help Book if I go to the Help menu and open it from there.
Furthermore, Apple's document states:
The NSAlert, SFChooseIdentityPanel, SFCertificatePanel classes provide
help buttons for dialogs. To display such a help button and link it to
an anchor in your help book, use the methods setShowsHelp: and
setHelpAnchor: in those classes.
and the documentation for these properties in NSAlert state:
-setShowsHelp:YES adds a help button to the alert panel. When the help button is pressed, the delegate is first consulted. If the delegate
does not implement alertShowHelp: or returns NO, then -[NSHelpManager
openHelpAnchor:inBook:] is called with a nil book and the anchor
specified by -setHelpAnchor:, if any. An exception will be raised if
the delegate returns NO and there is no help anchor set.
...so I know that I am using these two properly.
I also understand that I need to create a .helpindex file every time I update my Apple Help book HTML documents. I'm using "Help Indexer.app" which is in the Additional Xcode Tools on developer.apple.com. I make sure that:
I have the option set to index all anchors.
Any HTML page with an anchor has <meta name="ROBOTS" content="ANCHORS"> in the header so anchors are indexed.
My Apple Help book plist file correctly points to the .helpindex file created by "Help Indexer.app".
But even with all of this, I cannot get it to open the Apple Help book to the correct anchor or even the Title page of my Apple Help book.
I've read
https://developer.apple.com/library/archive/documentation/Carbon/Conceptual/ProvidingUserAssitAppleHelp/user_help_intro/user_assistance_intro.html#//apple_ref/doc/uid/TP30000903-CH204-CHDIDJFE
from cover to cover multiple times and I cannot find a solution or anywhere online.
I've also tried opening it manually, but it just opens to the same error saying the specified content couldn't be found with the following code:
let bookName = Bundle.main.object(forInfoDictionaryKey: "CFBundleHelpBookName") as! String
NSHelpManager.shared.openHelpAnchor("ArrivalTimesUsingStopID", inBook: bookName)
Using nil for the inBook parameter doesn't work either:
NSHelpManager.shared.openHelpAnchor("ArrivalTimesUsingStopID", inBook: nil)
Any ideas?

I'm not sure if this is the answer at this point, but it is an answer and one that seems to do the trick. I wasn't able to get the helpAnchor in the Alert to work, but using the help delegate, the method outlined below works.
I started out my day trying to open the Help Book to a simple anchor. I'm sure this used to work using the NSHelpManager in the past, but it does not appear to in recent versions of the OS.
Watching the console while opening my under-development App's help book resulted in the following:
Opening URL help:openbook=%22com.ClueTrust.Cartographica.help*1.5.2d1%22 with application <FSNode 0x6000006a1b40> { isDir = y, path = '/System/Library/CoreServices/HelpViewer.app' }
Opening to my anchor using NSHelpManager resulted in:
Opening URL help:anchor=SpatialJoinOperation%20bookID=%22com.ClueTrust.Cartographica.help%22%20appID=%22com.ClueTrust.Cartographica%22 with application <FSNode 0x6000006a8260> { isDir = y, path = '/System/Library/CoreServices/HelpViewer.app' }
And, it didn't result in opening to my anchor.
I tried appending the *<version> to my URL:
Opening URL help:anchor=SpatialJoinOperation%20bookID=%22com.ClueTrust.Cartographica.help*1.5.2d1%22%20appID=%22com.ClueTrust.Cartographica%22 with application <FSNode 0x600000682c20> { isDir = y, path = '/System/Library/CoreServices/HelpViewer.app'
Looking deeper into the Console, though, I noticed that this is definitely triggering a network request and there's an unsupported URL coming back.
It's not clear to me if help:anchor=... does not function any longer, but I did find a relatively easy, but annoying way around the problem.
Anchors within help will definitely be opened when using a help: URL that is formatted like a file: URL and contains an anchor; and they will open to the correct anchor location.
This requires locating the specific help book and HTML file so that you can specify precisely where to open.
NSURL *helpBookURL = [NSBundle.mainBundle URLForResource:#"Cartographica" withExtension:#"help"];
NSBundle *helpBundle = [NSBundle bundleWithURL:helpBookURL];
NSURL *helpPageURL = [helpBundle URLForResource:#"Spatial_Join" withExtension:#"html"];
NSURLComponents *urlParts = [NSURLComponents componentsWithURL:helpPageURL resolvingAgainstBaseURL:NO];
urlParts.scheme=#"help";
urlParts.fragment=#"SpatialJoinOperation";
NSURL *finalHelpURL = urlParts.URL;
[NSWorkspace.sharedWorkspace openURL:finalHelpURL];
Basically:
Get the URL for the help book (need to do this in a way that gets it from the resource path, hence we're using NSBundle)
Locate the page containing the reference based on prior knowledge (in this case Spatial_Join.html is our filename, so we have the bundle look for it by name and extension.
Use the NSURLComponents interface to mutate the NSURL by changing the scheme from file to help and adding our achor in the fragment.
Finally, open the newly-created URL
It's not pretty, but it does appear to be effective and safe, at least in a non-sandboxed macOS App under 10.15.
Note that I could make some assumptions here about the help book name, but for illustration purposes this seems more clear, and because of the way resources work, it's not clear that those assumptions about the names would be appropriate in all situations.
My final result was this helper method:
- (void)openHelpPage:(NSString*)pageName anchor:(NSString * _Nullable)anchor bookName:(NSString * _Nullable)bookName
{
NSURL *helpBookURL = [NSBundle.mainBundle URLForResource:bookName withExtension:#"help"];
NSBundle *helpBundle = [NSBundle bundleWithURL:helpBookURL];
NSURL *helpPageURL = [helpBundle URLForResource:pageName withExtension:#"html"];
NSURLComponents *urlParts = [NSURLComponents componentsWithURL:helpPageURL resolvingAgainstBaseURL:NO];
urlParts.scheme=#"help";
if (anchor)
urlParts.fragment=anchor;
NSURL *finalHelpURL = urlParts.URL;
[NSWorkspace.sharedWorkspace openURL:finalHelpURL];
}
Call site syntax is:
// to specific anchor on a page
[self openHelpPage: #"Spatial_Join" anchor: #"SpatialJoinOperation" helpBook: nil];
// to specific page
[self openHelpPage: #"Spatial_Join" anchor: nil helpBook: nil];
I tried getting the help bundle with [NSBundle bundleWithIdentifier:] using the help bundle ID, but that returned nil. However, [NSBundle URLForResource:withExtension] will take a nil argument for the resourceName and get the first item that matches the extension. In my case (and I believe many) there is only one help resource, so this allows for a method that doesn't require knowledge of the Application's help book name.

I was finally able to get this working in a sandboxed application.
If you're using a Help button directly, you can use something like:
#IBAction func helpButtonAction(_ sender: Any)
{
if let bookName = Bundle.main.object(forInfoDictionaryKey: "CFBundleHelpBookName") as? String {
NSHelpManager.shared.openHelpAnchor("MY_ANCHOR_HERE", inBook: bookName)
}
}
If you're using an NSAlert(), you can use its help button with an anchor this way:
let alert = NSAlert()
...
alert.showsHelp = true
alert.helpAnchor = NSHelpManager.AnchorName("MY_ANCHOR_HERE")
A few things I learned the hard way:
Make sure your HTML page for your Help Book has a the proper setup for an anchor with:
<meta name="robots" content="anchors"> in the <head> section as well as a proper header tag in the fashion of:
<a name="MY_ANCHOR_HERE"></a> in your <body> section.
Make sure you use "Help Indexer.app" to index your Help Book. I found that it will not work unless you index your Help Book using this app. This app can be downloaded from developer.apple.com under More Downloads. They usually release a new version with every Xcode update. You want to look for "Additional Tools" and the specific indexer app will be located in
Additional Tools > Utilities > Help Indexer.app
Additionally, macOS does not like when you have multiple Help Books. This means, multiple copies of your Application on your Mac no matter where they reside. This could be in your Debug folder and your Application folder as your most common places. I found that deleting the copy in my Applications folder usually helps macOS not get confused when opening a Help Book. I have also found it to open up older versions of the Help Book so it's best to make sure you only have once copy of your app on your Mac when debugging help books.
But other than that, they should open just fine with simply an Anchor string and a few lines of code depending on how you display your Help button!

Related

Hyperlinks in JSQMessagesViewController messages

I'm using the JSQMessages framework which is super awesome but I'm trying to enable hyperlinks in both the sent and received text messages - essentially i want to apply a find and replace system where certain words are replaced with pre determined hyperlinks, for example the word "stackoverflow" would turn into a hyperlink to stackoverflow when the user presses the send message button. The message receiver would then receive the hyperlink.
I have the find and replace system working fine however I'm struggling with the hyperlinks. As far as I can tell there doesn't seem to be any way to add hyperlinks to the messages as the "text" message element used to pass execute the message is an NSString and therefore cannot have a url attributed to it. However I hope I'm wrong, are hyperlinks, i.e. a word that links/executes a url when it's tapped, in text messages possible? If so what would be the recommended approach?
Thanks,
Matt
And for Swift 2.0 :
let attributes: [String:AnyObject] = [NSForegroundColorAttributeName:UIColor.redColor(), NSUnderlineStyleAttributeName: 1]
cell.textView!.linkTextAttributes = attributes
Please use following code in collectionView cellForItemAtIndexPath
Activate the data indicator as well..
cell.textView.dataDetectorTypes=UIDataDetectorTypeAll;
cell.textView.linkTextAttributes = #{ NSForegroundColorAttributeName : [UIColor jsq_messageBubbleBlueColor],
NSUnderlineStyleAttributeName : #(NSUnderlineStyleNone | NSUnderlineStyleNone) };

best way to show images stored in the app from a webview in SWIFT

I have a webview and would like to show an image in the webview (html)
My HTML :
hello !img src="myimage.png" alt="myimage" height="42" width="42"!
(I used ! as tagend and tagstart, because I don't know how to add this here without be interpreted as HTML, even I pasted as code)
The myimage.png should be stored in app itself and not be loaded from a websource.
I don't know how to do that in a best practice way. Any help ?
UPDATE
I tried with referenced Article, but still not succeeded:
My Code for this:
let path:NSString = NSBundle.mainBundle().bundlePath;
var baseURL:NSURL=NSURL(fileURLWithPath: path as String)!;
var htmlString:String! = texttemp
myWebView.loadHTMLString(htmlString, baseURL: baseURL)
The same Image I can already load like the following -> works:
var image = UIImage(named: "myimage.png");
Your updated code isn't right. You are creating a path to the bundle, not to the specific file. You need to use the NSBundle method pathForResource:ofType (or one of its variants) to build a path to your file. Then use that to create the URL.
The pathForResource:ofType family of methods return nil if the file can't be found, so you should check that you are getting back a path.
EDIT:
Looking at it more closely, I see that you are using the URL as the base URL for a call to loadHTMLString. This does look like a sound approach. What is your HTML string, and where is the image in your bundle?

How to access the content of a webpage displayed in a tab from a Firefox addon

In a Firefox extension, I am attempting to add a form into the web page by inserting DOM elements, and then process any data that the user enters in the form.
I have tried several methods, but have been unable to get my form inserted into the webpage's document. I tried using the different types of add-ons, Overlay (XUL) and the Add-on SDK, but I have not been able to get it to work.
Overlay/XUL: I investigated and found nothing that specifically showed me how to change the contents of a web page.
Using the Addons SDK: The only working code which I found was the demo code in "Modifying Web Pages Based on URL" which appeared to give:
var html = sth;
$("body").html(html);
I tried:
$('.id_of_ele').html('I want to show');
It doesn't work.
So far the only thing which has gotten me close is to use unsafeWindow.document, but I believe that is a really a bad idea, and the code looks really bad.
How do I access the the document of a webpage from a Firefox extension?
If you are looking for examples of known working code, you can always download one or more extensions from Mozilla Add-ons which do something close to what you want to accomplish and look at how they do it. Obviously, you should look at the license (linked on each extensions page) to see what the legal status of the code it. There are literally thousands of working examples there. The vast majority of which have code which is licensed in a way which permits you to re-use it.
The jQuery accesses which you are trying to use rely on the document variable pointing to the document which you are wanting to modify. In the context in which you are running, a Firefox add-on, the document variable may, by default, point to a document which is an ancestor of the webpage you are interested in or not be defined at all. What document actually is will depend on the context from which your add-on code was invoked. In a Firefox add-on, the document variable will almost never, by default, point to the content of a web page. You have to remember that you are writing code that is intended to run in a context that is much larger (entire browser/user agent) than that which is used for content scripts on a webpage (context within the browser is restricted to only the content of the webpage from which the script was run, or data which is obtained from references originating from within the page).
Gaining access to the document for the currently selected tab:
Changing the content document is very easy. You can change it just like you would from any JavaScript. The issue that you may find frustrating is obtaining a reference to the document.
Firefox overlay and restartless/bootstrapped have a great amount of power over the entire browser. However, the context, and what window points to, or even if it is defined, depends greatly on how the JavaScript was invoked. This can be both confusing and frustrating. On MDN, there is a document "Working with windows in chrome code" which describes many of the issues.
From extensions you have access to all windows and tabs. However, what you probably want is just some code that works to get you access to the current selected document.
This should work from all contexts to get you a reference to the document for the currently selected tab:
var selectedTabWindow = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
var selectedTabDocument = selectedTabWindow.content.document;
If you have code which you are converting from a content script which just expects to find window and document objects, you could write something like:
if (typeof window === "undefined") {
var window;
} else {
//Keep a reference to whatever was defined as window.
var originalWindow = window;
}
//Get the window from the most recently selected tab.
window = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//Now that we have a window for the most recently selected tab,
// get the document for it.
if (typeof document === "undefined") {
var document;
} else {
//Keep a reference to whatever was defined as document.
var originalDocument = document;
}
document = window.content.document;
//Now that we have a window for the most recently selected tab,
// get the gBrowser for it.
if (typeof gBrowser === "undefined") {
var gBrowser;
} else {
//Keep a reference to whatever was defined as gBrowser.
var originalGBrowser = gBrowser;
}
gBrowser = window.gBrowser;
The above will, obviously, overwrite any currently variables currently defined as window, document and gBrowser. Depending on the context in which you are running, and the scope in which you define these this could be either a good thing, or it might be a bad idea to change that reference. For example, if the code is running in a popup window then window is a reference to window of the popup. In that case, you can get a reference to the window from which the popup was opened with:
var windowWhichOpendedThisOne = window.opener;
var documentForWindowWhichOpendedThisOne = window.opener.content.document;
If you are in an event handler, then you can get the window for the target of the event from:
var windowInWhichEventTargetExists = event.view;
Choosing what to do based on the URL:
Once you have the correct document it should be quite easy to choose what to do based on the document's URL:
var currentUrl = document.location.href;

Programmatically launching OS X's Contacts app showing a contact?

Let's say that I've just created an ABPerson record and managed to save it in the user's address book. How do I programmatically open the default application which handles the address book (which most likely is Contacts but in some cases it might be Outlook or some other app) and show the new address book record I've just added?
Thanks in advance.
The addressbook URL scheme is able to show the person record or edit it:
ABPerson * aPerson = <#assume this exists#>;
// Open the Contacts app, showing the person record.
NSString * urlString = [NSString stringWithFormat:#"addressbook://%#", [aPerson uniqueId]];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
More information is in Address Book Programming Guide.
Here is my take using the Contacts app, and in Swift, written as an extension for CNContact. I expect most people are using Contacts in preference to AddressBook nowadays.
(CNContact's identifier is the same as ABPerson's uniqueId.)
func showInContacts() {
let path =
"/Users/someusername/Library/Application Support/AddressBook/Sources/05A62A31-9C1F-423F-A9F4-011E56EAAF29/Metadata/0A1F4FC2-7E01-4A40-92DE-840F8C84DE58:ABPerson.abcdp
var url = URL(fileURLWithPath: path)
url.deleteLastPathComponent()
url.appendPathComponent(self.identifier)// self is a CNContact
url.appendPathExtension("abcdp")
NSWorkspace.shared.open(url)
}
Contacts are in separate files buried at the end of a long chain of sub-folders in user's Library/Application Support. The file names are simply the contact's identifier plus an extension. You can save some typing by dragging one of them to your Xcode editor, surrounding with quotes, and maybe removing the last path component. As my app isn't for distribution that is enough for me; otherwise you will have to do some doctoring: the user's name will be in the second path component. I don't know the significance of the long ID number following 'Sources', whether it is user or system specific, but it is the only item in that subfolder, so you should be able to build a viable path programatically.

Creating Bookmarks

Today I'm working with an interesting problem. I'm trying to code simple bookmarks, and by that I mean the most basic functionality; store the url, and load the url when you tap it.
Right now, the idea is that I would use the following:
currentURL = currentWebView.request.URL.absoluteString;
To retrieve the current URL, and then possibly store that within NSDefaults.
(Open to any other suggestions on better ways of preforming this)
However, the problem that I'm having is that, assuming I've gotten that far (retrieved the URL and stored it), how would I go about putting then somewhere in a list format that displays the url that was bookmarked, and then loading them (from that separate bookmarks view) in the main view that the UIWebView is contained in?
Your time is much appreciated
--Jake
NOTE: If you need more information or anything I could possibly help you with in order to come to a solution, just ask
Simple
Suppose your viewcontroller where your webview resides, is WebViewController and where list shows is BookmarkViewController.
Create a property in BookmarkViewController
#property(nonatomic,retain)WebViewController *maincontroller;
Send the reference of your WebViewController to BookmarkViewController when you create BookmarkViewController object
bookmarkobject.maincontroller=self;
and then In didSelectRowAtIndexPath
either use
1) [maincontroller.webView loadRequest:url];
or
make a method in WebViewController with parameter url and call this method from didSelectRowAtIndexPath
2) [maincontroller loadUrlInWebView:url];
and dismiss your modal
or create a property of url in WebViewController and in didSelectRowAtIndexPath
3) [maincontroller urlstring:url];
and in viewWillAppear load this url in your webview
Hope any of these 3 methods will help you.

Resources