Objective-C: Grab title of web page from WebView - objective-c

I'm trying to retrieve the title of a web page and display it in the NSWindows title. This application is document based and I haven't tried anything in a standalone application with an AppDelegate. How would I go forth and retrieve the title and display it in the window?
UPDATE: Here's my code (Note: Doesn't work quite yet)
TitleWindow.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface TitleWindow : NSWindow <NSApplicationDelegate> {
}
#property
(retain, nonatomic) IBOutlet NSWindow *displayTitle;
#end
TitleWindow.m
#import "TitleWindow.h"
#import <WebKit/WebKit.h>
#implementation TitleWindow
- (void)displayTitle:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
{
if (frame == [sender mainFrame]){
[[sender window] setTitle:title];
}
}
#end

To get an NSWebViews page title.
You use it's mainFrameTitle
NSString * pageTitleString =[_webView mainFrameTitle];
NSLog(#"%#" , pageTitleString);
Read the Docs
example 2.
WebFrameLoadDelegate_Protocol
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)webFrame{
[_window setTitle:[_webView mainFrameTitle]];
}
UPDATE 2
I have not really built a document based app before. So setting one up to display a web view was fun :-P.
Which leads me to think the problem is two fold.
The first thing I released is just adding the example 2 code to the document.m is not enough.
The webview needs a delegate.
What worked for me and may not be (probably not) the right way to do it was in the Document.xib with the webview selected.
The Connections Inspector at the top shows you the frameLoadDelegate.
I connected this to the file's Owner by dragging it over to the file's Owner in the Place holders pane.
This worked.
And each time I changed the webpage from within the document the title changed along with it.
The second thing to note is I think the real method of setting the documents title is:
[self setDisplayName:#"a title"];
which works when set in:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has loaded the document's window.
NSURLRequest* request = [NSURLRequest requestWithURL:_theUrls];
[[_webView mainFrame] loadRequest:request];
[self setDisplayName:#"a title"];
}
And a normal string, But if I change it to:
[self setDisplayName:[_webView mainFrameTitle]];
It does not work as the frame is not loaded yet so there is no title.
So you would think then that [self setDisplayName:[_webView mainFrameTitle]]; would work in the WebFrameLoadDelegate. No it does not. And I am not sure why.
So for now you may want to use the code in example 2 code.

Related

Minimize / miniaturize cocoa NSWindow without titlebar

I'm stuck! Obviously... because I'm posting a question here.
I have built my own custom window controls for my OS X / cocoa app. The close button works great -- no problems. The minimize button doesn't work at all when I disable the titlebar.
So when the title bar is on like the image above and I hit this method, the minimizing works fine:
ViewController.h
#interface ViewController : NSViewController {
- (IBAction)minimize:(id)sender;
#property (strong) IBOutlet NSButton *btn_minimize;
}
#end
ViewController.m
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)minimize:(id)sender {
[[NSApp mainWindow] performMiniaturize:self];
}
-(IBAction)terminate:(id)sender {
[NSApp terminate:self];
}
#end
Then if I disable the title bar, that same method stops working. No errors, nothing. I've tried both: [[NSApp mainWindow] miniaturize:nil]; and also [[NSApp mainWindow] performMiniaturize:self];. Neither of which worked. Actually... the both work IF the title bar is on. But once I turn it off, neither work.
Thoughts / comments?
Oh, I'm using Storyboards, Xcode 7, and am targeting 10.10 and using the 10.11 SDK if that matters at all.
Thanks in advance.
You have to keep the original "traffic light" buttons around and hide them manually.
Here's how I've configured the window:
self.titleVisibility = NSWindowTitleHidden;
self.titlebarAppearsTransparent = YES;
self.movableByWindowBackground = YES;
And here's how I've hidden the traffic lights:
for (id subview in self.contentView.superview.subviews) {
if ([subview isKindOfClass:NSClassFromString(#"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) {
if ([button isKindOfClass:[NSButton class]]) {
[button setHidden:YES];
}
}
}
}
See sample project here.
In my case, I wanted to completely remove the title bar and trigger the miniaturization through some other means (eg: a key assignment).
I found that ensuring that the window style mask includes NSMiniaturizableWindowMask was required in order for NSWindow::miniaturize to have an effect.
Adding to Wez Furlong's answer, this is how to do it in Swift 5.
Assuming you have an NSWindow named "theWindow", do the following:
theWindow.styleMask = theWindow.styleMask.union(.miniaturizable)
Basically, you perform a set union of the window's existing style mask with the .miniaturizable option.

Presenting modal dialogs from XIB in Cocoa: best/shortest pattern?

Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.

Can't get UITouch event from UIButton

Problem:
I can't get my UIButton to send my Class an Event call
Here is what I tried first:
I made a class which extends UIViewController, called Viewer
#import "Viewer.h"
#import "MainScreen.h"
#implementation Viewer
- (void)viewDidLoad
{
MainScreen* main = [[MainScreen alloc]init: self];
[main show];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
then:
I created a class called MainScreen, 2 IBOutlets - singlePlayerButton and view
Viewer is an instance of UIViewController
buttonPressed has the return type of IBAction
MainScreen:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
{
IBOutlet UIButton* singlePlayerButton;
IBOutlet UIView* view;
Viewer* viewer;
}
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
}
- (IBAction)buttonPressed:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Then I created an NIB called MainScreenLayout.xib
Here is what it looks like:
As you can see, I have four buttons but the only one that is connected to anything is the Single-Player button I also have the UIView connected to view.
Now, I linked the action to the Single-Player button like this:
buttonPressed is my method that I want to have called when the button is pressed, so I ctrl-clicked on First Responder and then clicked on the circle next to buttonPressed and then dragged it on top of the Single-Player button. Then the next dialog window popped up and I clicked on Touch up Inside. Here is what the Received Actions window looks like:
Now, I thought at this point it should work. I ran it on my iPhone Simulator and pressed the button.
Just so there is no question of whither or not I'm competent, here is a picture of me actually clicking the button:
I got no output whatsoever. Then I went to my code and looked at the method. This is what it looks like:
as you can see, the oval is not filled, meaning that the method is not paired. So naturally I thought it was being overridden by another method. So here is what the method looks like now:
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
Then I went back to my NIB window, unpaired my Single-Player button and then repaired it to aVerySpecificMethodNameForMyButtonListener. Now this is what it looks like:
Then I ran it again and still nothing when I pressed the button. Then I went back to the code:
(insert frustration here)
At this point I restarted Xcode and the Simulator and looked over everything again and it still didn't work.
This is what I tried next which seems about a billion times more simple:
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
I replaced with my show method with the one above. The only thing different is the last line. I disconnected my method from the Single-Player button in the NIB. Then I ran it and I got excited for a second because I got some output. Here is what I got:
(lldb)
Very helpful output C debugger, as always. I spent the next few hours on SO trying to find out what I'm doing wrong. I've tried countless examples and none of them worked.
Does anybody see what I'm doing wrong here? Is it just Xcode being flakey?
Thanks for reading!! All relevant answers are welcomed!
UPDATE:
I also tried changing the declaration of UIButton to a property in the Header file:
#import <Foundation/Foundation.h>
#import "Viewer.h"
#interface MainScreen : NSObject
{
IBOutlet UIView* view;
Viewer* viewer;
}
#property (weak, nonatomic) IBOutlet UIButton* singlePlayerButton;
- (id)init:(Viewer*) theViewer;
- (void)show;
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender;
#end
Thus here is what I changed in MainScreen.m to comply:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
#synthesize singlePlayerButton;
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Now there are no variable declarations inside of the MainScreen.m file, I moved them all to the Header. The circles next to singlePlayerButton and view are still filled in but the bubble next to the method is not. I ran the code again and got the same output.
So I'd have put this in a comment because its really just a suggestion however I dont have the rep yet so sorry to get your hopes up, but I believe the touch event's scope is only inside the UIView in which it happens. When the touch up event gets triggered it doesn't get passed to the UIView's handler (MainScreen class) and so the event's listener method does not get called... but that you are able to link it in the interface builder to the button is baffling to me, so i could be way off.
Anyways, where I think you should go next is to make your MainScreen a UIView object and see what that does for event handling of the buttons in the view.

Display window again after closing it and setting Focus

I have two short questions:
How is it possible to reopen a window after closing it.
How do I set the focus on the window I open.
It would be nice if someone can help me!
Here is the source code I have so far but I don't know how to go on:
The method calling the WindowController:
- (IBAction)openPreferences:(id)sender
{
[NSApp activateIgnoringOtherApps:YES];
if (NULL == preferences)
{
preferences = [[PreferencesController alloc] initWithWindowNibName:#"Preferences"];
}
[preferences showPreferenceWindow];
}
This is the Header of PreferencesController:
#import <Foundation/Foundation.h>
#interface PreferencesController : NSWindowController <NSWindowDelegate>
- (void)showPreferenceWindow;
#end
This is the Main of PreferencesController:
#import "PreferencesController"
#interface PreferencesController()
#end
#implementation PreferencesController
- (void)windowWillClose:(NSNotification *)notification
{
}
// display the preference window
- (void)showPreferenceWindow
{
[self.window makeKeyAndOrderFront:NSApp];
// TODO: window should be focused and if the user press the close button it should be displayed again
}
- (void) dealloc
{
[super dealloc];
}
#end
NSWindow has this handy method - (void)setReleasedWhenClosed:(BOOL)releasedWhenClosed. Set that to NO and you window can open unlimited amounts of times. As to your focus issue: It should already be working to bringing your window to focus.
But since you are using a preference window: may I suggest DBPrefsWindowController it is pretty old but it still works today.

Displaying an UIImageView property from another class programmatically

Well, here's the situation:
I've got...
a custom class with an UIImageView-property, let's call it the Enemy-class
a ViewController
a NSMutableArray to help create multiple instances of Enemy (called enemies)
and what I want:
be able to create an unlimited amount of Enemy-Instances through a method in my ViewController (like [self spawnEnemy];, where self is the ViewController)
and, subsequently, display the UIImageView property (let's call it "enemyImage") on the view that is controlled by my ViewController
I've tried something like this:
-(Enemy *) spawnEnemy
{
Enemy *tempEnemy = [Enemy new];
[enemies addObject:(Enemy*)tempEnemy];
[self.view addSubview:(UIImageView*)[[enemies objectAtIndex:[enemies count]] enemyImage]];
//randomLocation is declared in the Enemy-Class and just assigns a random
//CGPoint to self.enemyImage.center
[[enemies objectAtIndex:[enemies count]] randomLocation];
return [[enemies objectAtIndex:[enemies count]]createEnemy];
}
This runs without errors, randomLocation gets called (tried with NSLog), AND if I do something like this in another Method of ViewController:
[[self spawnEnemy] enemyTestMethod];
enemyTestMethod is being executed as well.
But still, no enemieViews are displayed on the screen...
What am I doing wrong?
Thank you so much for your help and time.
==== Edit ====
Here's the relevant code from Enemy.h/Enemy.m:
#interface Enemy : NSObject
{
UIImageView *enemyImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *enemyImage;
-(Enemy*) createEnemy;
//Enemy.m
#implementation Enemy
#synthesize enemyImage, speed;
-(Enemy *) createEnemy
{
self.enemyImage = [[UIImageView alloc] init];
[self.enemyImage setImage:[UIImage imageNamed:#"enemy.png"]];
return self;
}
I also corrected the last line in the spawnEnemy-Method to properly send createEnemy.
You don't include the code in Enemy where you alloc/init the UIImageView property. Unless this code explicitly specifies a CGRect with the size and origin that you want, the view will be initialized with CGRectZero, which means even if you're correctly adding the subview (and it looks like you are) you still won't see it anywhere.
Post the Enemy code, and the problem will probably be immediately apparent.
Have you called -createEnemy before you added them to your view?
OK, you've got this checked.
Then maybe you should check as #MusiGenesis suggested.
To do this, you need to inspect the properties of your enemyImage.
You can do this in either of the following ways:
print its frame.size by:
NSLog(#"enemyImage frame size: %#", NSStringFromCGRect(enemy.enemyImage));
set a breakpoint where you feel great, and check with your debugger:
p (CGRect)[[enemy enemyImage] frame]