Getting the current URL and title from WebView - xcode

I am trying to get the current URL and title from the WebView. I've used this for the URL
- (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
{
// Only report feedback for the main frame.
if (frame == [sender mainFrame]){
NSString *url = [[[[frame provisionalDataSource] request] URL] absoluteString];
[addressBar setStringValue:url];
}
}
and this for the title:
- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
{
// Report feedback only for the main frame.
if (frame == [sender mainFrame]){
[[sender window] setTitle:title];
}
}
This code comes straight from Apple's WebKit Objective-C Programming Guide. I only slightly modified the URL method to the addressBar instead of textField. But it doesn't work. The addressBar field is never populated with the URL of the page and the window title doesn't update ether. Everything is connected correctly in interface builder. Why won't it work?

If the page has already loaded then it is not a provisionaldatasource, replace this with
[[[[frame dataSource] request] URL] absoluteString];

Unfortunately, I myself don't know. But what I have found is a great website with tutorials one of which is to make an RSS feed and in that it takes the title from the RSS. You'll see it if your scroll down. Hope you can modify it to work!!
http://www.raywenderlich.com/2636/how-to-make-a-simple-rss-reader-iphone-app-tutorial

Here's you you get this working.
Set up your frameLoadDelegate for the WebView object. In your visual interface, control-drag from your WebView element to your NSWindow that is containing the WebView. When you release the mouse, a small black drop-down menu will appear. Select frameLoadDelegate. Once you've done this, messages such as webView:DidReceiveTitle:forFrame will be sent to the instance of your NSWindow.
Create a Subclass out of NSWindow and assign your NSWindow object to this subclass. Since this new child object will inherit everything from NSWindow, it will receive the webView:DidReceiveTitle:forFrame message.
Paste in your code above into this new child class. This effectively overrides the method definitions from the parent class, and gives you autonomous control over what happens.
Hope that helps.

Related

How to use NSViewController in an NSDocument-based Cocoa app

I've got plenty of experience with iOS, but Cocoa has me a bit confused. I read through several Apple docs on Cocoa but there are still details that I could not find anywhere. It seems the documentation was written before the NSDocument-based Xcode template was updated to use NSViewController, so I am not clear on how exactly I should organize my application. The template creates a storyboard with an NSWindow, NSViewController.
My understanding is that I should probably subclass NSWindowController or NSWindow to have a reference to my model object, and set that in makeWindowControllers(). But if I'd like to make use of the NSViewController instead of just putting everything in the window, I would also need to access my model there somehow too. I notice there is something called a representedObject in my view controller which seems like it's meant to hold some model object (to then be cast), but it's always nil. How does this get set?
I'm finding it hard to properly formulate this question, but I guess what I'm asking is:how do I properly use NSViewController in my document-based application?
PS: I understand that NSWindowController is generally meant to managing multiple windows that act on one document, so presumably if I only need one window then I don't need an NSWindowController. However, requirements might change and having using NSWindowController may be better in the long run, right?
I haven't dived into storyboards but here is how it works:
If your app has to support 10.9 and lower create custom of subclass NSWindowController
Put code like this into NSDocument subclass
- (void)makeWindowControllers
{
CustomWindowController *controller = [[CustomWindowController alloc] init];
[self addWindowController:controller];
}
If your app has multiple windows than add them here or somewhere else (loaded on demand) but do not forget to add it to array of document windowscontroller (addWindowController:)
If you create them but you don't want to show all the windows then override
- (void)showWindows
{
[controller showWindow:nil]
}
You can anytime access you model in your window controller
- (CustomDocument *)document
{
return [self document];
}
Use bindings in your window controller (windowcontroller subclass + document in the keypath which is a property of window controller)
[self.textView bind:#"editable"
toObject:self withKeyPath:#"document.readOnly"
options:#{NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName}];
In contrast to iOS most of the views are on screen so you have to rely on patterns: Delegation, Notification, Events (responder chain) and of course MVC.
10.10 Yosemite Changes:
NSViewController starting from 10.10 is automatically added to responder chain (generally target of the action is unknown | NSApp sendAction:to:from:)
and all the delegates such as viewDidLoad... familiar from iOS are finally implemented. This means that I don't see big benefit of subclassing NSWindowCotroller anymore.
NSDocument subclass is mandatory and NSViewController is sufficient.
You can anytime access you data in your view controller
- (CustomDocument *)document
{
return (CustomDocument *)[[NSDocumentController sharedDocumentController] documentForWindow:[[self view] window]];
//doesn't work if you do template approach
//NSWindowController *controller = [[[self view] window] windowController];
//CustomDocument *document = [controller document];
}
If you do like this (conforming to KVC/KVO) you can do binding as written above.
Tips:
Correctly implement UNDO for your model objects in Document e.g. or shamefully call updateChangeCount:
[[self.undoManager prepareWithInvocationTarget:self] deleteRowsAtIndexes:insertedIndexes];
Do not put code related to views/windows into your Document
Split your app into multiple NSViewControllers e.g.
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:AAPLListWindowControllerShowAddItemViewControllerSegueIdentifier]) {
AAPLListViewController *listViewController = (AAPLListViewController *)self.window.contentViewController;
AAPLAddItemViewController *addItemViewController = segue.destinationController;
addItemViewController.delegate = listViewController;
}
}
Previous code is called on windowcontroller with viewcontroller as delegate (again possible only after 10.10)
I always prefer to use multiple XIBs rather than one giant storyboard/XIB. Use following subclass of NSViewController and always inherit from it:
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property(strong) IBOutlet NSView *viewToSubstitute;
#end
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)awakeFromNib
{
NSView *view = [self viewToSubstitute];
if (view) {
[self setViewToSubstitute:nil];
[[self view] setFrame:[view frame]];
[[self view] setAutoresizingMask:[view autoresizingMask]];
[[view superview] replaceSubview:view with:[self view]];
}
}
#end
Add a subclass of MyViewController to the project with XIB. Rename the XIB
Add NSViewController Object to the XIB and change its subclass name
Change the loading XIB name to name from step 1
Link view to substitute to the view you want to replace
Check example project Example Multi XIB project
Inspire yourself by shapeart or lister or TextEdit
And a real guide is to use Hopper and see how other apps are done.
PS: You can add your views/viewcontroller into responder chain manually.
PS2: If you are beginner don't over-architect. Be happy with the fact that your app works.
I'm relatively new to this myself but hopefully I can add a little insight.
You can use the view controllers much as you would in ios. You can set outlets and targets and such. For NSDocument-based apps you can use a view controller or the window controller but I think for most applications you'll end up using both with most of the logic being in the view controller. Put the logic wherever it makes the most sense. For example, if your nsdocument can have multiple window types then use the view controller for logic specific to each type and the window controller for logic that applies to all the types.
The representedObject property is primarily associated with Cocoa bindings. While I am beginning to become familiar with bindings I don't have enough background to go into detail here. But a search through the bindings programming guide might be helpful. In general bindings can take the place of a lot of data source code you would need to write on ios. When it works it's magical. When it doesn't work it's like debugging magic. It can be a challenge to see where things went wrong.
Let me add a simple copy-pastable sample for the short answer category;
In your NSDocument subclass, send self to the represented object of your view controller when you are called to makeWindowControllers:
- (void) makeWindowControllers
{
NSStoryboard* storyboard = [NSStoryboard storyboardWithName: #"My Story Board" bundle: nil];
NSWindowController* windowController = [storyboard instantiateControllerWithIdentifier: #"My Document Window Controller"];
MyViewController* myController = (id) windowController.contentViewController;
[self addWindowController: windowController];
myController.representedObject = self;
}
In you MyViewController subclass of NSViewController, overwrite setRepresentedObject to trap it's value, send it to super and then make a call to refresh your view:
- (void) setRepresentedObject: (id) representedObject
{
super.representedObject = representedObject;
[self myUpdateWindowUIFromContent];
}
Merci, bonsoir, you're done.

WebView - Failed to set (contentViewController) user defined inspected property on (NSWindow)

I create a new Xcode project from scratch and it compiles fine.
I add a label to the View Controller and this compiles / runs / shows fine.
I then drag in a WebView into the View Controller but get this message when I run the application:
Failed to set (contentViewController) user defined inspected property on (NSWindow): *** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (WebView)
What am I doing wrong?
Just Link Webkit.framework in general->Linked Framework and Libraries
Well first of all you need to define the Web view so it actually knows what todo all it is doing right now is trying to find what to run here, see a tutorial on youtube or such, this is the code i used for mine, just to set it up.
first you want to put the web view into the view controller and add a toolbar to the bottom of it.
add four bar button items onto the bar.
and put a fixed space in-between the two.
then you need to set the styles of them do this by going to attributes inspector> button type > choose style
choose the following for the button 1st one is rewind 2nd is forward 3rd one is refresh last one is Cancel.
then control click and drag the button to the web view it will give you the options for it like load rewind and refresh.
then go to your storyboard and make sure you have you main and header files connected do this by selected the view controller in the storyboard files, then
change the class to the view controller you want to hook up to.
then hit enter
go to your header file(.h) and insert the following code,
#property (nonatomic, strong) IBOutlet UIWebView *webView;
put it under the #interface with brackets but make sure that the code is not inside of the brackets.
then what you need to do is go to your main file(.m) and insert the following code like this
#interface WebViewController ()
#end
#implementation WebViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
//custom initialization
}
return self;
}
- (void)viewDidLoad {
NSURL *url = [NSURL URLWithString:#"https://www.google.com"];
NSURLRequest *requestURL = [NSURLRequest requestWithURL:url]; [_webView loadRequest:requestURL];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
accept obviously you need to replace the void or just past into the right places
Then build and Run your code use iOS simulator iPhone 6.
Hope this helps have a nice day!
You should be using the wrong property webFrame of WebView instead of mainFrame
if let url = NSBundle.mainBundle().URLForResource("index", withExtension: "html"){
editorWebView.mainFrame.loadRequest(NSURLRequest(URL: url))
}
I don't think you are doing anything wrong. Its just a bug

Hide/Unhide UINavigationbar when the screen is tapped

I'm very new with iOS Development and I have just created one of my first apps, in my .xib file I have a UINavigationBar that I want to hide/show when a part of the screen is tapped by the user (like in the Photo app). I've found some snippets online but I don't know where and how to use those.
I'd appreciate a lot if somebody could give me detailed informations about how to do this.
Add this toggle method anywhere in your UIViewController. This hides on first tap and shows again in second tap.
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navigationController.navigationBar.hidden;
[self.navigationController setNavigationBarHidden:!barsHidden animated:YES];
}
If there is no navigation controller, link the navigation bar with an IBOutlet and replace with
- (void)toggleNavBar:(UITapGestureRecognizer *)gesture {
BOOL barsHidden = self.navBar.hidden;
self.navBar.hidden = !barsHidden;
}
Then add the following in the method -(void)viewDidLoad {}
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(toggleNavBar:)];
[self.view addGestureRecognizer:gesture];
[gesture release];
If the view where you are going to tap is a UIWebViewController, you have to add the protocol to the view controller and set it as delegate gesture.delegate = self; then add the following:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
This is needed because the UIWebViewController already implements its own gesture recognizers.
Ultimately, you want to send the -setHidden: message to your navigation bar. The easiest way to do this is to make an Outlet and an Action in your in your view controller. Then, in your .xib file, connect the navigation bar to the outlet and some button (even a large, full screen one) to the action.
Outlets and Actions are basic techniques used over and over in iOS
(and Mac) programming, so if you don't understand them, best go read
up on them now. Every beginning iOS/Mac programming book covers this
topic as does Apple's own Getting Started guide (pay particular
attention to the Configuring the View section).
Inside your action, send a message to the outlet like so:
-(void)myButtonAction:(id)sender{
[[self myNavigationBarOutlet] setHidden:YES];
}
This will hide the navigation bar whenever your button is tapped.
(This assumes you have a UINavigationBar in your .xib like you say. These directions will be different if you're working with a UINavigationController that manages its own UINavigationBar)

How do I embed a WebView in an NSScrollView?

So I have a project that has some content being displayed in a WebView and I want to stick above that. I want the header to scroll with the WebView content. Now, WebView generally wants to do it's own scroll handling but you can tell it not to using:
[[webView mainFrame] setAllowsScrolling:NO];
which successfully makes the WebView's scroll bars not appear. However, despite the fact that it's embedded in an NSScrollView, the NSScrollView's scroll bars never activate. I can only conclude that the WebView needs to be told to resize, but I can't figure out how to do that. I have tried the following:
NSRect webViewBounds = [webView bounds];
[webView setFrameSize:webViewBounds.size];
but that doesn't appear to work either.
Any suggestions?
Alternative solution: What about expressing the header in HTML/CSS/JavaScript, and putting the real content in an iframe? You can traverse the frame hierarchy to find the WebFrame object for that frame, and use the DOM (perhaps wrapped by an object of your own) to control the header.
I had a quick look at the docs for this, probably a silly question; after you change the frame size are you sending a setNeedsDisplay message to the view?
The bounds and frame in your code should have the same size. Setting one to the other changes nothing.
Today I played a little bit with the WebView class. With my approach you wouldn’t have to add your header to the HTML code. Registering for a notification gives you a starting point:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(webViewDidScroll:)
name:NSViewBoundsDidChangeNotification object:nil];
Then you can check the class of the notification sender (let’s assume you have only one WebView):
- (void)webViewDidScroll:(NSNotification *)aNotification
{
if ([[aNotification object] isKindOfClass:NSClassFromString(#"WebClipView")])
{
// Do whatever you like; scroll your header etc.
}
}

How do you put a normal control into an NSView?

What I'm actually trying to do is put a WebKitView into a ScreenSaver (which inherits NSView). I'm totally new to MacOS X and Cocoa (but I'm very familiar with Objective-C and used some parts of GNUStep). Do I need some laying out? I want to show only one control in the whole NSView.
In your initWithFrame:isPreview: method, create a WebView in the usual way, then, send yourself an addSubview: message, passing the web view:
webView = [[WebView alloc] initWithFrame:frame];
[self addSubview:webView];
If you're wondering what rectangle you should pass as the WebView's frame, read the View Programming Guide. Also, don't forget to release the webView in dealloc (or autorelease it in initWithFrame:isPreview:).
Once you have your web view, you'll need to load a page into it. Set a suitably long animation interval (at least a couple of seconds), and load the page in animateOneFrame:
- (void) animateOneFrame {
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com/"]]];
}
You may also want to handle WebViewProgressFinishedNotification, and put off re-loading the web view until that happens (to compensate for slow or soaked connections). You'll do this with an instance variable, which you set to YES in both initWithFrame:isPreview: and your notification-handler method, and test and set to NO in animateOneFrame:
- (void) animateOneFrame {
if (hasFinished) {
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com/"]]];
hasFinished = NO;
}
}
[aScreenSaverView addSubview:aWebKitView];
But why add a UIWebView into a screen saver view when you can just make the UIWebView take up the full screen on its own? Introducing view hierarchies where they are not needed is not a good idea because it increases the processing needed to display the interface.
You can also not worry too much about your animation interval by calling
[self stopAnimation];
at the end of your animateOneFrame method.

Resources