Cocoa WebView scrollbars won't disappear - cocoa

I load the webview and set allowsScrolling to NO, but webview still shows scroll bars... Banging your head on your computer hurts a lot more now that MacBooks have sharp metal edges.
My code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSString *webFolder = #"file:///<WebFolderPath>";
[[[productWeb mainFrame] frameView] setAllowsScrolling:NO];
[productWeb setFrameLoadDelegate:self];
[[productWeb mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[webFolder stringByAppendingString:#"webpage.html"]]]];
}
I even setup the frame loading delegate to report about the scrolling status:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
[[frame frameView] setAllowsScrolling:NO];
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
}
Which still gives me the unhappy:
2010-08-24 15:20:09.102 myApp[30437:a0f] Scrolling Allowed
2010-08-24 15:20:09.104 myApp[30437:a0f] Scrolling Not Allowed
And yet the scrollbars continue to show! Hopefully, it is something stupid I'm doing as I don't want to get any more blood on my laptop.

I found I had to edit the HTML of the page I was trying to display to make sure that it was setup to take the full screen (and not more)... there was a DIV that had a minimum width set. Once I made sure the body had height = 100% and that none of the DIV's had a fixed or minimum width set that was smaller then the box I wanted to show it in everything came together :)

Are you trying to prevent the user from scrolling the view at all? You can just set productWeb.userInteractionEnabled = NO. Or are you just trying to prevent the bars from showing when the user is scrolling?
Here's another thing you can try: inject some JavaScript into your UIWebView that disables the touchmove event:
[productWeb stringByEvaluatingJavaScriptFromString:#"document.ontouchmove = function(e){e.preventDefault();}"];
This leaves the user interaction enabled, but should prevent any scrolling.

Try this if you use Swift:
import Foundation
import Cocoa
import WebKit
class WebViewRep: WebView {
//... hidden code
required init?(coder: NSCoder) {
super.init(coder: coder)
//Disable scrolling
self.mainFrame.frameView.allowsScrolling = false
//Sets current object as the receiver of delegates
self.policyDelegate = self
self.frameLoadDelegate = self
//Load homepage
self.loadHomePage()
}
//... hidden code
override func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!) {
//Completely disable scroll
frame.frameView.allowsScrolling = false
self.stringByEvaluatingJavaScriptFromString("document.documentElement.style.overflow = 'hidden';")
}
}
Sadly, it's not enough to use allowScrolling. It works sometimes, but not always. So, you have to manipulate loaded page too.
Of course, don't forget to set WebViewRep as custom class for your WebView.

I'm guessing your web view is in a nib that's being loaded before -applicationDidFinishLaunching: is called.
A quick fix:
In your nib, select the window and deselect "Visible at Launch" attribute.
Then after you call -setAllowsScrolling:, call -[self.window makeKeyAndOrderFront:nil] to show the window.
A better fix is to move your window loading code from the app delegate to a window controller subclass, do the call to -setAllowsScrolling: in -[NSWindowController windowDidLoad], and call -[NSWindowController showWindow:] from your app delegate to show the window.

Related

NSViewController disable resize when presented as Sheet?

So I am presenting an NSViewController as a sheet of a window that has resize disabled.
The view controller that is presented as a sheet can still be resized.
How do I disable resizing of a NSViewController?
Swift 4:
override func viewDidAppear() {
// any additional code
view.window!.styleMask.remove(.resizable)
}
By the way you can do this without writing code, here is how:
Drag a Window Controller element to the Storyboard from the Object
Library.
Connect the Window Controller to the specific View Controller which you want to disable resize.
On the Window Controller's Attributes uncheck Resize option.
After some more trying I found out this did the trick in viewDidLoad:
self.preferredContentSize = NSMakeSize(self.view.frame.size.width, self.view.frame.size.height);
If you add these methods, the issue will be fixed.
- (void)updateViewConstraints NS_AVAILABLE_MAC(10_10) {
[super updateViewConstraints];
}
- (void)viewWillLayout NS_AVAILABLE_MAC(10_10) {
self.preferredContentSize = NSMakeSize(self.view.frame.size.width, self.view.frame.size.height);
}

Can UIPopoverPresentationController be forced to reposition popover instead of resizing it?

I am using auto layout with Storyboard. I present a popoverPresentationController from a cell rect:
NumberController * viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NumberController"];
UIPopoverPresentationController *pc = [viewController popoverPresentationController];
pc.delegate = self;
pc.permittedArrowDirections = UIPopoverArrowDirectionAny;
pc.sourceView = tableView;
pc.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
[self.navigationController presentViewController:viewController animated:animated completion:nil];
The popover presents on an iPad in portrait mode with the arrow up.
I rotate the iPad to landscape mode. The popoverPresentationController keeps the same sourceView/sourceRect and properly points to the cell. It also keeps the up arrow.
But it is now at the bottom of the view, so the popover resizes to a shorter height. This is not desired behavior.
If the popover were simply to move to a new position and change the arrow direction, it would not need to resize at all. This is the desired behavior.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change:
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
}
I have tried to reset the permittedArrowDirections (in preferredContentSize, because this seemed like the most logical place). This does not work (the popover still resizes):
- (CGSize) preferredContentSize {
[super preferredContentSize];
self.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUnknown;
return CGSizeMake(DEFAULT_POPOVER_WIDTH,DEFAULT_POPOVER_HEIGHT);
}
I simply cannot find a way to force the popoverPresentationController to change arrow direction and reposition the popover instead of resizing the popover. I am beginning to think it is not even possible - but I still hold out hope that I am just missing something.
EDIT: In the meantime, it has occurred to me that maybe a popover is not the best way to present this view if I don't want it resized in iPad. I am going to try it with UIModalPresentationFormSheet presentation. But I would still like to find an answer to this question.
I just ran into the problem where
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
was not being called because my view controller was detached. There may be a view in your view hierarchy whose view controller has not been added as a child view controller.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change
The sourceView rect does not have to change, just the interface orientation. From the UIPopoverControllerDelegate documentation:
For popovers that were presented using the presentPopoverFromRect:inView:permittedArrowDirections:animated: method, the popover controller calls this method when the interface orientation changes.

NSTabViewController ignoring transitions and title propagation settings

I am trying to create a preferences panel for my app using storyboards and the new NSTabViewController class.
I can get it working, but the transition setting in the storyboard seems to be ignored. It just jumps from one tab to the next, with the size of the window changing instantaneously.
I thought it might depend on whether I use autolayout or not, but it didn't seem to change the transition behavior when I toggled it.
I also have the 'Propagates title' setting checked. I had expected that it would take the label of the tab item, or the title of the view controller, and propagate that as the window title, but it doesn't seem to do that.
Anyone got this to work?
Here is a simple sample app I am testing with: https://www.dropbox.com/s/roxaplxy5gtlqns/Again.zip?dl=0
Update: Got this working thanks to Pierre. Ended up making a nice transitioning preferences window by subclassing NSTabViewController as follows:
#implementation MCPreferencesTabViewController
-(void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[super tabView:tabView willSelectTabViewItem:tabViewItem];
NSTabViewItem *currentTabItem = tabView.selectedTabViewItem;
currentTabItem.view.hidden = YES;
tabViewItem.view.hidden = YES;
NSWindow *window = self.view.window;
NSSize contentSize = tabViewItem.view.fittingSize;
NSSize newWindowSize = [window frameRectForContentRect:(CGRect){CGPointZero, contentSize}].size;
NSRect frame = [window frame];
frame.origin.y += frame.size.height;
frame.origin.y -= newWindowSize.height;
frame.size = newWindowSize;
[self.view.window setFrame:frame display:NO animate:YES];
}
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[super tabView:tabView didSelectTabViewItem:tabViewItem];
tabViewItem.view.hidden = NO;
}
#end
You need to make the NSTabViewController the delegate of the NSTabView.
In Interface Builder, control-drag from No Shadow Tab View to Tab View Controller and set the delegate outlet.
One would expect Interface Builder to set this up correctly when creating a new tab view controller. It does not.
Please note that this was true until Xcode 9.
Since Xcode 9, you need to remove (or not add it) this line:
self.tabView.delegate = self
otherwise you will receive an error:
*** Assertion failure in -[YourApp.CustomTabView setDelegate:],
/BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.0.100/AppKit.subproj/NSTabView.m:2766
2017-10-25 19:29:06.301282+0200 YourApp[23106:5687795] Failed to set (contentViewController) user defined inspected property on (NSWindow):
A TabView managed by a TabViewController cannot have its delegate modified
Or in viewDidLoad() for the NSTabViewController, include
self.tabView.delegate = self

Status Bar returns when UIImagePickerViewController is called?

Everything in my app is working a treat - but there is one niggling problem.
The UIImagePickerController seems to return the status bar when it is called. Obviously the app has the statusbar hidden throughout.
Now I have worked around this by rehiding it upon completion or canelation of the picker. This resulted in a black bar at the top of the app. So after the rehide I have had to reposition the titlebar and other table contents for it to fit.
All in all this works perfectly fine. However, the UIImagePickerController is called in detail view of a table. Therefore when the user has used the picker (and ive resized after use) and clicks the back button to return to the main table there is a small graphical glitch.
The detail view has been shifted up to hide the statusbar void, yet when I return to the main table and the app slides horizontally back to the main view, for a split second a 20px black box can be seen above the items on the detail view?
To recap. UIImagePickerController returns the staus bar (seemingly no matter what) and after coding to get rid and reformat the view I get a time (messy) graphical issue when returning to the main view.
Surely there is a way to stop the statusbar returning so I dont have to bodge bar back out using code? I have it set 'off' in the plist.
It very odd! Cheers
This helps to me.
1) You must delegate the UIImagePickerController
2) Add this to ViewController:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{ // Esconder el StatusBar. Provocado por el iOS7 y el UIImagePickerController
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
}
implementing UIimagepicker controller use this.and hide status bar in to plist(statusbar initialy hidden=true) and set Uiview size 320x480 & implementing this
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
{
UIImagePickerController *picker= [[UIImagePickerController alloc]init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:picker animated:YES];
[picker release];
}

Custom NSView in NSMenuItem not receiving mouse events

I have an NSMenu popping out of an NSStatusItem using popUpStatusItemMenu. These NSMenuItems show a bunch of different links, and each one is connected with setAction: to the openLink: method of a target. This arrangement has been working fine for a long time. The user chooses a link from the menu and the openLink: method then deals with it.
Unfortunately, I recently decided to experiment with using NSMenuItem's setView: method to provide a nicer/slicker interface. Basically, I just stopped setting the title, created the NSMenuItem, and then used setView: to display a custom view. This works perfectly, the menu items look great and my custom view is displayed.
However, when the user chooses a menu item and releases the mouse, the action no longer works (i.e., openLink: isn't called). If I just simply comment out the setView: call, then the actions work again (of course, the menu items are blank, but the action is executed properly). My first question, then, is why setting a view breaks the NSMenuItem's action.
No problem, I thought, I'll fix it by detecting the mouseUp event in my custom view and calling my action method from there. I added this method to my custom view:
- (void)mouseUp:(NSEvent *)theEvent {
NSLog(#"in mouseUp");
}
No dice! This method is never called.
I can set tracking rects and receive mouseEntered: events, though. I put a few tests in my mouseEntered routine, as follows:
if ([[self window] ignoresMouseEvents]) { NSLog(#"ignoring mouse events"); }
else { NSLog(#"not ignoring mouse events"); }
if ([[self window] canBecomeKeyWindow]) { dNSLog((#"canBecomeKeyWindow")); }
else { NSLog(#"not canBecomeKeyWindow"); }
if ([[self window] isKeyWindow]) { dNSLog((#"isKeyWindow")); }
else { NSLog(#"not isKeyWindow"); }
And got the following responses:
not ignoring mouse events
canBecomeKeyWindow
not isKeyWindow
Is this the problem? "not isKeyWindow"? Presumably this isn't good because Apple's docs say "If the user clicks a view that isn’t in the key window, by default the window is brought forward and made key, but the mouse event is not dispatched." But there must be a way do detect these events. HOW?
Adding:
[[self window] makeKeyWindow];
has no effect, despite the fact that canBecomeKeyWindow is YES.
Add this method to your custom NSView and it will work fine with mouse events
- (void)mouseUp:(NSEvent*) event {
NSMenuItem* mitem = [self enclosingMenuItem];
NSMenu* m = [mitem menu];
[m cancelTracking];
[m performActionForItemAtIndex: [m indexOfItem: mitem]];
}
But i'm having problems with keyhandling, if you solved this problem maybe you can go to my question and help me a little bit.
Add this to your custom view and you should be fine:
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
I added this method to my custom view, and now everything works beautifully:
- (void)viewDidMoveToWindow {
[[self window] becomeKeyWindow];
}
Hope this helps!
I've updated this version for SwiftUI Swift 5.3:
final class HostingView<Content: View>: NSHostingView<Content> {
override func viewDidMoveToWindow() {
window?.becomeKey()
}
}
And then use like so:
let item = NSMenuItem()
let contentView = ContentView()
item.view = HostingView(rootView: contentView)
let menu = NSMenu()
menu.items = [item]
So far, the only way to achieve the goal, is to register a tracking area manually in updateTrackingAreas - that is thankfully called, like this:
override func updateTrackingAreas() {
let trackingArea = NSTrackingArea(rect: bounds, options: [.enabledDuringMouseDrag, .mouseEnteredAndExited, .activeInActiveApp], owner: self, userInfo: nil)
addTrackingArea(trackingArea)
}
Recently I needed to show a Custom view for a NSStatusItem, show a regular NSMenu when clicking on it and supporting drag and drop operations on the Status icon.
I solved my problem using, mainly, three different sources that can be found in this question.
Hope it helps other people.
See the sample code from Apple named CustomMenus
In there you'll find a good example in the ImagePickerMenuItemView class.
It's not simple or trivial to make a view in a menu act like a normal NSMenuItem.
There are some real decisions and coding to do.

Resources