WKWebView on macOS cuts off top - swift

I placed a WKWebView inside a NSView with a y coordinate > 0. After loading a page (no matter which one) it either immediately cuts off the top or it shows the top for a second and then jumps down the page (by doing so cutting off the top).
What's more is that when I scroll up I get a glimpse of the top portion but it bounces back not allowing me to actually scroll to the very top.
No changes have been made to the WKWebView. What I noticed is, that the smaller y the smaller the portion which is cut off.
The WKWebView has been added via the Interface Builder.
How can I make the WKWebView show the top of the website? I'm using Swift.
This is what it looks like:
This is what the actual website looks like (notice the visible navigation section):

Okay, I had the same problem as you! Check your view's frame on construction and, if it's not starting at 0,0, make sure that the parent view doesn't auto resize the subviews. I tried to keep my code sample to the bare minimum. (note that webConfig.AllowsAirPlayForMediaPlayback isn't required in this sample)
Here's a working example. You can switch the view to be full frame in the window or positioned as a smaller view in the parent. I set the window to be 1024x768 in IB.
You can easily convert this code to Swift.
The key is to not autoresize the subviews if the view is being placed at an offset other than 0,0.
quick sample:
//#define USE_FULL_WINDOW // uncomment this to fit webview full window
using System;
using AppKit;
using Foundation;
using WebKit;
using CoreGraphics;
using System.Diagnostics;
namespace WkWebTest
{
public class MediaView : NSView
{
private WKWebView webView;
public MediaView(CGRect frame)
: base(frame)
{
#if USE_FULL_WINDOW
this.AutoresizesSubviews = true;
this.AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable;
#endif
WKWebViewConfiguration webConfig = new WKWebViewConfiguration();
webConfig.AllowsAirPlayForMediaPlayback = true;
this.webView = new WKWebView(new CGRect(0, 0, frame.Width, frame.Height), webConfig);
this.webView.AutoresizesSubviews = true;
this.webView.AutoresizingMask = NSViewResizingMask.HeightSizable | NSViewResizingMask.WidthSizable;
this.AddSubview(this.webView);
string url = "http://www.apple.com";
Debug.WriteLine("LoadUrl: " + url);
var request = new NSUrlRequest(new NSUrl(url));
this.webView.LoadRequest(request);
}
public override bool IsFlipped { get { return true; } }
}
public partial class ViewController : NSViewController
{
private MediaView mediaView;
public ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
#if USE_FULL_WINDOW
float mediaViewLeft = 0;
float mediaViewTop = 0;
nfloat mediaViewWidth = View.Frame.Width;
nfloat mediaViewHeight = View.Frame.Height;
#else
float mediaViewLeft = 511;
float mediaViewTop = 112;
nfloat mediaViewWidth = 475;
nfloat mediaViewHeight = 548;
#endif
this.mediaView = new MediaView(new CGRect(mediaViewLeft, mediaViewTop, mediaViewWidth, mediaViewHeight));
this.View.AddSubview(mediaView);
}
// boiler plate code generated by Xamarin
public override NSObject RepresentedObject
{
get
{
return base.RepresentedObject;
}
set
{
base.RepresentedObject = value;
// Update the view, if already loaded.
}
}
}
}

Related

How can I make sure my Xamarin.Forms iOS custom renderer of my entry uses the element's width?

In my Xamarin.Forms application I use a custom renderer for entries, since I want them to be made of a single, bottom border. The problem is that I can't find out the right code to make the custom renderer use the element's width. Currently, the situation is like this:
As you can see, the bottom border goes far beyond the real element's width, but I don't understand why. Here I found something, but still I don't understand how to fix this and why this happens. My current code for the renderer class is:
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
var view = Element as CustomEntry;
var borderLayer = new CALayer
{
Frame = new CGRect(0f, Frame.Height + 5, Frame.Width, 1f),
BorderColor = UIColor.Gray.CGColor,
BackgroundColor = UIColor.Magenta.CGColor,
BorderWidth = 13,
MasksToBounds = true
};
Control.Layer.AddSublayer(borderLayer);
}
}
}
The problem seems to be in that Frame.Width. If I set it to 100, for example, the width of the bottom line of the entry is set to 100, but the problem is that, doing so, I'm not able to horizontally center the line. I want this outcome:

How to find the cursor Y position in an UITextView having multiple lines as word wraps?

I need to find the cursor position or the focus position in an UiTextView with multiple lines.
You can get the cursor's CGPoint (X and Y position) within a UITextView in different ways. But do you need to find the position of cursor in relation to self.view (or phone screen borders)? If so, I translated this answer to C# for you:
var textView = new UITextView();
UITextRange selectedRange = textView.SelectedTextRange;
if (selectedRange != null)
{
// `caretRect` is in the `textView` coordinate space.
CoreGraphics.CGRect caretRect = textView.GetCaretRectForPosition(selectedRange.End);
// Convert `caretRect` in the main window coordinate space.
// Passing `nil` for the view converts to window base coordinates.
// Passing any `UIView` object converts to that view coordinate space.
CoreGraphics.CGRect windowRect = textView.ConvertRectFromCoordinateSpace(caretRect, null);
}
else
{
// No selection and no caret in UITextView.
}
You can get the current position and current rect of cursor by using following codes:
public partial class ViewController : UIViewController
{
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
UITextView textF = new UITextView();
textF.Frame = new CoreGraphics.CGRect(30,20,200,50);
textF.Text = "testtesttesttesttesttesttesttesttesttest";
textF.Delegate = new myTextDelegate();
View.Add(textF);
}
}
public class myTextDelegate : UITextViewDelegate {
public override bool ShouldChangeText(UITextView textView, NSRange range, string text)
{
//To get the current Position
var startPoint = textView.BeginningOfDocument;
var selectRange = textView.SelectedTextRange;
var currentPoint = textView.GetOffsetFromPosition(startPoint, selectRange.Start);
Console.WriteLine(currentPoint);
//To get the current Rect
CoreGraphics.CGRect caretRect = textView.GetCaretRectForPosition(selectRange.End);
Console.WriteLine(caretRect);
return true;
}
}
Refer: etting-and-setting-cursor-position-of-uitextfield-and-uitextview-in-swift and cursor-position-in-relation-to-self-view

Touch Gesture Recognizer to clear screen of random function

I am building a collage in Xcode with a set background image and a random int function to display an image.
I tried using a tapGestureRecognizer to 1) display the random image and 2) clear the screen after using self.clearScreen. The images are displaying but they do not disappear when the next image is displayed.
import UIKit
import C4
class WorkSpace: CanvasController {
override func setup() {
var myRandomNumber: Int?
//Background Image
let background = Image("background")
background?.constrainsProportions = true
background?.width = self.canvas.width
background?.height = self.canvas.height
self.canvas.add(background)
canvas.addTapGestureRecognizer { (locations, center, state) in
self.clearScreen
myRandomNumber = random(below: 4)
}
}
if myRandomNumber == 1 {
let tree = Image("Tree")
tree?.constrainsProportions = true
tree?.width = self.canvas.width
tree?.origin = Point(0.0, self.canvas.height/2.0)
self.canvas.add(tree)
print("number1")
}
if myRandomNumber == 2 {
let boy = Image("boy")
boy?.constrainsProportions = true
boy?.width = self.canvas.width
boy?.origin = Point(0.0, self.canvas.height/2.0)
self.canvas.add(boy)
}
if myRandomNumber == 3 {
let woman = Image("woman")
woman?.constrainsProportions = true
woman?.width = self.canvas.width
woman?.height = self.canvas.height
self.canvas.add(woman)
}
}
}
I would like the screen to clear the pictures and replace with another.
func clearScreen() {
for view in self.view.subView {
view.removeFromSuperView()
}
}
After looking at the C4 pod docs:
Check the view hierarchy after adding to canvas
/// Adds a view to the end of the receiver’s list of subviews.
/// When working with C4, use this method to add views because it handles the addition of both UIView and View.
public func add<T>(_ subview: T?) {...}
And then try removing subviews with their supplied methods:
/// Unlinks the view from the receiver and its window, and removes it from the responder chain.
/// Calling this method removes any constraints that refer to the view you are removing, or that refer to any view in the
/// subtree of the view you are removing.
/// When working with C4, use this method to add views because it handles the removal of both UIView and View.
/// ````
/// let v = View(frame: Rect(0,0,100,100))
/// let subv = View(frame: Rect(25,25,50,50))
/// v.add(subv)
/// v.remove(subv)
/// ````
/// - parameter subview: The view to be removed.
public func remove<T>(_ subview: T?) {
if let v = subview as? UIView {
v.removeFromSuperview()
} else if let v = subview as? View {
v.view.removeFromSuperview()
} else {
fatalError("Can't remove subview of class `\(type(of: subview))`")
}
}

How to set the window size and position programmatically for a SpriteKit/GameScene app on OSX

I have a bare-bones project created in Xcode as a SpriteKit/GameScene app. I want to set the window size programmatically. I've read a lot of answers here, and several tutorials elsewhere, but none of the things I've read have helped.
This answer talks about overriding WindowController.windowDidLoad, but GameScene doesn't give me a WindowController. It does give me a ViewController. This tutorial says you can call self.view.window?.setFrame() from ViewController.viewDidLoad(), but my window stubbornly remains the same size. A number of the answers I've found on SO talk about auto-layout. I don't have anything to lay out. It's just a SpriteKit starter project.
This answer says you can set preferredContentSize in ViewController.viewWillAppear(), and that does in fact let me set the window size, but if I make the window too big (because I had to guess at a legitimate size), it's not draggable or resizable. This answer says you can get the correct size from view.bounds.size, but that says 800 x 600, which is nowhere near the size of my screen. This answer says you can get the bounds from UIScreen.mainScreen().bounds.size, but my GameScene/SpriteKit starter project seems not to have any kind of UIScreen in it.
How can I get the screen size, and how can I set the window size and position programmatically?
Also, one of the other answers, which I can't seem to find any more, says you should delete GameScene.sks, which I did, and everything seems fine still, except for the size.
Updated for Swift 5
Window Size
Remember to add NSWindowDelegate to your NSViewController class if you wish to implement it there (ie. viewDidLoad(), viewWillAppear(), viewDidAppear(), etc.)
NSView
class ViewController: NSViewController, NSWindowDelegate {
override func viewWillAppear() {
fillWindow()
}
/// Sizes window to fill max screen size
func fillWindow() {
if let screenSize = view.window?.screen?.frame {
view.window!.setFrame(screenSize, display: true)
}
}
}
NSWindow
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
fillWindow()
}
/// Sizes window to fill max screen size
func fillWindow() {
if let screenSize = window?.screen?.frame {
window!.setFrame(screenSize, display: true)
}
}
}
Print to Debugger Console
print(screenSize) // embed within if let screenSize { ... }
Window Position
See the full answer, with implemented code, here.
extension NSWindow {
/// Positions the `NSWindow` at the horizontal-vertical center of the `visibleFrame` (takes Status Bar and Dock sizes into account)
public func positionCenter() {
if let screenSize = screen?.visibleFrame.size {
self.setFrameOrigin(NSPoint(x: (screenSize.width-frame.size.width)/2, y: (screenSize.height-frame.size.height)/2))
}
}
/// Centers the window within the `visibleFrame`, and sizes it with the width-by-height dimensions provided.
public func setCenterFrame(width: Int, height: Int) {
if let screenSize = screen?.visibleFrame.size {
let x = (screenSize.width-frame.size.width)/2
let y = (screenSize.height-frame.size.height)/2
self.setFrame(NSRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height)), display: true)
}
}
/// Returns the center x-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
/// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
public func xCenter() -> CGFloat {
if let screenSize = screen?.visibleFrame.size { return (screenSize.width-frame.size.width)/2 }
if let screenSize = screen?.frame.size { return (screenSize.width-frame.size.width)/2 }
return CGFloat(0)
}
/// Returns the center y-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
/// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
public func yCenter() -> CGFloat {
if let screenSize = screen?.visibleFrame.size { return (screenSize.height-frame.size.height)/2 }
if let screenSize = screen?.frame.size { return (screenSize.height-frame.size.height)/2 }
return CGFloat(0)
}
}
Usage
NSWindow
Positions the existing window to the center of visibleFrame.
window!.positionCenter()
Sets a new window frame, at the center of visibleFrame, with dimensions
window!.setCenterFrame(width: 900, height: 600)
NSView
Using xCenter() and yCenter() to get the central x-y points of the visibleFrame.
let x = self.view.window?.xCenter() ?? CGFloat(0)
let y = self.view.window?.yCenter() ?? CGFloat(0)
self.view.window?.setFrame(NSRect(x: x, y: y, width: CGFloat(900), height: CGFloat(600)), display: true)
It would be awesome if some UI guru could comment on whether this is the "right" answer, but here's what I did as a wild guess based on "El Tomato's" rather cryptic comments. First, add the following to ViewController.swift:
class WildGuessWindowController: NSWindowController {
override func windowDidLoad() {
if let screenSize = window?.screen?.frame {
window!.setFrame(screenSize, display: true)
print(screenSize)
}
super.windowDidLoad()
}
}
Next, open Main.storyboard in the project navigator. You might see something like the following:
Click on the box that says "Window Controller", and open the right-hand side panel. You should see something like this.
Notice the box next to the arrow, that has a grayed-out class name in it. (Also notice that you might have to click the "Identity inspector" button, the blue one above where it says "Custom Class".) Now click that drop-down, and you should see WildGuessWindowController. Select that, and you're set. Build and run. The framework will instantiate your class, and run windowDidLoad().

NSTextField not drawing correctly in custom NSView

I have a custom NSView and I place three NSTextFields into the view programmatically (axes labels for a graph, basically):
var axesTextFields : Array<NSTextField> = [NSTextField(),NSTextField(),NSTextField()]
func addTextFields() {
axesTextFields[0].stringValue = "x"
axesTextFields[1].stringValue = "y"
axesTextFields[2].stringValue = "z"
for tf in axesTextFields {
tf.textColor = NSColor.blackColor()
tf.bezeled = false
tf.editable = false
tf.drawsBackground = false
self.addSubview(tf)
}
}
I update the location of the NSTextFields in the drawRect() function:
override func drawRect(fullRect: NSRect)
{
... // drawing lines for the graph
axesTextFields[0].frame = NSMakeRect(0,axes_location * 3 - 10,20,20)
axesTextFields[1].frame = NSMakeRect(0,axes_location * 2 - 10,20,20)
axesTextFields[2].frame = NSMakeRect(0,axes_location - 10,20,20)
}
The view sometimes draws correctly, but many times it looks like this (with the NSTextFields corrupted on the left):
When I drag the window to resize it, it gets drawn correctly most of the time:
I'm not sure where I'm going wrong.