NSDocument printOperationWithSettings not showing all pages - cocoa

In NSDocument subclass, have this function:
override func printOperationWithSettings(printSettings: [String : AnyObject]) throws -> NSPrintOperation {
let printInfo: NSPrintInfo = self.printInfo
var pageSize = printInfo.paperSize
pageSize.width -= printInfo.leftMargin + printInfo.rightMargin
pageSize.height -= printInfo.topMargin + printInfo.bottomMargin
pageSize.width = pageSize.width * 2
pageSize.height = pageSize.height * 2
let myPage = MyPage(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: pageSize))
let printOperation = NSPrintOperation(view: myPage, printInfo: printInfo)
return printOperation
}
MyPage is, for this test, an NSView subclass that just draws an oval.
class MyPage: NSView {
override var flipped: Bool {
return true
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.greenColor().set() // choose color
let figure = NSBezierPath() // container for line(s)
figure.appendBezierPathWithOvalInRect(self.frame)
figure.stroke() // draw line(s)
}
}
I'd expect this to show four pages in the print panel, but it only shows two, equating to the top left and bottom left of the oval. No matter how wide I make myPage's frame, only the leftmost pages are shown. Any ideas why? Thank you!

Related

Reset offset, onTapGesture works, but onRotated does not work, why?

I want the view containing 2 rectangles floating in the bottom of the screen, whatever the orientation is portrait or landscape.
code is a test, when orientationDidChangeNotification happened, I Found UIDevice.current.orientation.isPortrait and UIScreen.main.bounds.height often have wrong value, why?
Anyway, test code is just reset offset = 0 in onRotated(). but it doesn't work; otherwise onTapGesture works fine.
Q1: Is it a wrong way for SwiftUI? SceneDelegate.orientationDidChangeNotification -> contentView.onRotated()?
Q2: why do UIDevice.current.orientation.isPortrait and UIScreen.main.bounds.height often have wrong value?
Q3: How to let a view float at the bottom of screen in both portrait and landscape?
let height: CGFloat = 100
struct TestView: View {
#State var offset = (UIScreen.main.bounds.height - height) / 2
var body: some View {
ZStack {
Text("+")
VStack(spacing: 0) {
Rectangle().fill(Color.blue)
Rectangle().fill(Color.red)
}
.frame(width: 100, height: height)
.offset(y: offset)
.onTapGesture {
self.offset = 0
}
}
}
func onRotated() {
// let isPortrait = UIDevice.current.orientation.isPortrait
offset = 0//(UIScreen.main.bounds.height - height) / 2
// print("\(isPortrait), screen height = \(UIScreen.main.bounds.height)")
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = TestView()
if let windowScene = scene as? UIWindowScene {
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { notification in
contentView.onRotated()
}
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
...
}
contentView is not a reference, it is a value, so you call .onRotated on own copy of contentView value that lives only within callback
let contentView = TestView()
if let windowScene = scene as? UIWindowScene {
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { notification in
contentView.onRotated() // local copy!!!
}
instead create listener for notification publisher inside TestView, so it can change self internally.
Moreover, it is not clear the intention but SwiftUI gives possibility to track size classes via EnvironmentValues.horizontalSizeClass and EnvironmentValues.verticalSizeClass which are automatically changed on device orientation, so it is possible to make your view layout depending on those environment values even w/o notification.
See here good example on how to use size classes

Run an SKSpriteNode object along a line drawn with the mouse in Swift

If you let go of the left mouse button, my SKSpriteNode object should move along the line previously "drawn" with the mouse.
My problem is that my sprite node is moving down when I release the left mouse button.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let object: SKSpriteNode = SKSpriteNode(imageNamed: "rabbit")
var actionArray = [SKAction()]
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
physicsWorld.contactDelegate = self
object.setScale(0.2)
object.position = CGPoint(x: size.width / 2, y: size.height / 2)
object.zPosition = 0
object.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "rabbit"), size: object.size)
object.physicsBody?.mass = 0
object.physicsBody?.categoryBitMask = 0
object.physicsBody?.contactTestBitMask = 1
object.physicsBody?.collisionBitMask = 0
addChild(object)
}
override func mouseDragged(with event: NSEvent) {
actionArray.append(SKAction.move(to: event.location(in: self), duration: 1))
}
override func mouseUp(with event: NSEvent) {
object.run(SKAction.sequence(actionArray))
// Remove all actions from 'actionArray'.
actionArray.removeAll()
}
}
I have already tried it as shown below. Now my sprite node moves to the first point, where I pressed down the left mouse button.
override func mouseUp(with event: NSEvent) {
let removeAllFromActionArray = SKAction.run {
self.actionArray.removeAll()
}
actionArray.append(removeAllFromActionArray)
object.run(SKAction.sequence(actionArray))
}
I've already tried view! .Convert (event.locationInWindow, to: view! .Scene!) Instead of event.location (in: self), as indicated in Position of mouse click relative to scene, not window, but the result was the same.
I suspect that the bug lies in override func mouseUp(with event: event).
Where does the error lie or is there another solution?

Rotate NSImageView at its Center to Make it Spin

Swift 4, macOS 10.13
I have read a variety of answers on SO and still can't get an NSImageView to spin at its center instead of one of its corners.
Right now, the image looks like this (video): http://d.pr/v/kwiuwS
Here is my code:
//`loader` is an NSImageView on my storyboard positioned with auto layout
loader.wantsLayer = true
let oldFrame = loader.layer?.frame
loader.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
loader.layer?.position = CGPoint(x: 0.5, y: 0.5)
loader.layer?.frame = oldFrame!
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(-1 * .pi * 2.0)
rotateAnimation.duration = 2
rotateAnimation.repeatCount = .infinity
loader.layer?.add(rotateAnimation, forKey: nil)
Any ideas what I am still missing?
I just created a simple demo which contains the handy setAnchorPoint extension for all views.
The main reason you see your rotation from a corner is that your anchor point is somehow reset to 0,0.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var imageView: NSImageView!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
// Create red NSImageView
imageView = NSImageView(frame: NSRect(x: 100, y: 100, width: 100, height: 100))
imageView.wantsLayer = true
imageView.layer?.backgroundColor = NSColor.red.cgColor
window.contentView?.addSubview(imageView)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationDidBecomeActive(_ notification: Notification) {
// Before animate, reset the anchor point
imageView.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
// Start animation
if imageView.layer?.animationKeys()?.count == 0 || imageView.layer?.animationKeys() == nil {
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = 0
rotate.toValue = CGFloat(-1 * .pi * 2.0)
rotate.duration = 2
rotate.repeatCount = Float.infinity
imageView.layer?.add(rotate, forKey: "rotation")
}
}
}
extension NSView {
func setAnchorPoint(anchorPoint:CGPoint) {
if let layer = self.layer {
var newPoint = NSPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = NSPoint(x: self.bounds.size.width * layer.anchorPoint.x, y: self.bounds.size.height * layer.anchorPoint.y)
newPoint = newPoint.applying(layer.affineTransform())
oldPoint = oldPoint.applying(layer.affineTransform())
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.anchorPoint = anchorPoint
layer.position = position
}
}
}
As I wondered many times myself on this question, here is my own simple method to rotate any NSView. I post it also as a self reminder. It can be defined in a category if needed.
This is a simple rotation, not a continuous animation. Should be applied to an NSView instance with wantsLayer = YES.
- (void)rotateByNumber:(NSNumber*)angle {
self.layer.position = CGPointMake(NSMidX(self.frame), NSMidY(self.frame));
self.layer.anchorPoint = CGPointMake(.5, .5);
self.layer.affineTransform = CGAffineTransformMakeRotation(angle.floatValue);
}
This is the result of a layout pass resetting your view's layer to default properties. If you check your layer's anchorPoint for example, you'll find it's probably reset to 0, 0.
A simple solution is to continually set the desired layer properties in viewDidLayout() if you're in a view controller. Basically doing the frame, anchorPoint, and position dance that you do in your initial setup on every layout pass. If you subclassed NSImageView you could likely contain that logic within that view, which would be much better than putting that logic in a containing view controller.
There is likely a better solution with overriding the backing layer or rolling your own NSView subclass that uses updateLayer but I'd have to experiment there to give a definitive answer.

How can I make SKSpriteNode positions the same for any simulator/device?

In my game, the position of my SKNodes slightly change when I run the App on a virtual simulator vs on a real device(my iPad).
Here are pictures of what I am talking about.
This is the virtual simulator
This is my Ipad
It is hard to see, but the two red boxes are slightly higher on my iPad than in the simulator
Here is how i declare the size and position of the red boxes and green net:
The following code is located in my GameScene.swift file
func loadAppearance_Rim1() {
Rim1 = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake((frame.size.width) / 40, (frame.size.width) / 40))
Rim1.position = CGPointMake(((frame.size.width) / 2.23), ((frame.size.height) / 1.33))
Rim1.zPosition = 1
addChild(Rim1)
}
func loadAppearance_Rim2(){
Rim2 = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake((frame.size.width) / 40, (frame.size.width) / 40))
Rim2.position = CGPoint(x: ((frame.size.width) / 1.8), y: ((frame.size.height) / 1.33))
Rim2.zPosition = 1
addChild(Rim2)
}
func loadAppearance_RimNet(){
RimNet = SKSpriteNode(color: UIColor.greenColor(), size: CGSizeMake((frame.size.width) / 7.5, (frame.size.width) / 150))
RimNet.position = CGPointMake(frame.size.width / 1.99, frame.size.height / 1.33)
RimNet.zPosition = 1
addChild(RimNet)
}
func addBackground(){
//background
background = SKSpriteNode(imageNamed: "Background")
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
self.addChild(background)
}
Additionally my GameViewController.swift looks like this
import UIKit
import SpriteKit
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
//Configure the view
let skView = view as! SKView
//If finger is on iphone, you cant tap again
skView.multipleTouchEnabled = false
//Create and configure the scene
//create scene within size of skview
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
//scene.anchorPoint = CGPointZero
//present the scene
skView.presentScene(scene)
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .Landscape
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
How can I make the positions of my nodes be the same for each simulator/physical device?
You should round those floating point values to integers via a call to (int)round(float) so that the values snap to whole pixels. Any place where you use CGPoint or CGSize should use whole pixels as opposed to floating point values.
If you are making a Universal application you need to declare the size of the scene using integer values. Here is an example:
scene = GameScene(size:CGSize(width: 2048, height: 1536))
Then when you initialize the positions and sizes of your nodes using CGPoint and CGSize, make them dependant on SKScene size. Here is an example:
node.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
If you declare the size of the scene for a Universal App like this:
scene.size = skView.bounds.size
then your SKSpriteNode positions will be all messed up. You may also need to change the scaleMode to .ResizeFill. This worked for me.

How does one manually tell NSTextContainer to recalculate the lineFragmentRects?

In my application, I have been adding support for NSTextView allowing subviews to work naturally with the text editor. (For example, pressing "enter" drops every subview lower than the insertion point, and text wraps around subviews.)
I have successfully been able to get word wrap around subviews by overridinglineFragmentRectForProposedRect(proposedRect: NSRect,
sweepDirection: NSLineSweepDirection,
movementDirection: NSLineMovementDirection,
remainingRect: NSRectPointer) -> NSRect.
However, when a subview is dragged, or resized, the breaks in the text need to be recalculated to get the desired effect. The text needs to update itself to wrap around the subview as it is dragged or resized. However, I noticed that typing on a line updates the lineFragmentRect for that line. Is there a way to immediately mark the text as "dirty" after the subview is dragged? Here is the code for the text container subclass:
import Cocoa
class CASTextContainer: NSTextContainer {
var mainView: CASTextView = CASTextView()
var textSubviews: Array<AnyObject> {
get {
return mainView.subviews
}
}
override var simpleRectangularTextContainer: Bool {
get {
return false
}
}
func rectForActiveRange() -> NSRect {
let range = layoutManager.glyphRangeForCharacterRange(textView.selectedRange, actualCharacterRange:nil)
var rect = layoutManager.boundingRectForGlyphRange(range, inTextContainer: self)
rect = NSOffsetRect(rect, textView.textContainerOrigin.x, textView.textContainerOrigin.y)
return rect;
}
override func lineFragmentRectForProposedRect(proposedRect: NSRect,
sweepDirection: NSLineSweepDirection,
movementDirection: NSLineMovementDirection,
remainingRect: NSRectPointer) -> NSRect
{
remainingRect.initialize(NSZeroRect)
var frames: Array<NSRect> = []
if textSubviews.isEmpty {
let fullRect = NSMakeRect(0, 0, containerSize.width, containerSize.height)
return NSIntersectionRect(fullRect, proposedRect)
}
for view in textSubviews {
frames.append(view.frame)
}
// Sort the array so that the frames are ordered by increasing origin x coordinate.
let fullRect = NSMakeRect(0, 0, containerSize.width, containerSize.height)
let region = NSIntersectionRect(fullRect, proposedRect)
let sortedFrames: Array<NSRect> = sorted(frames, { (first: NSRect, second: NSRect) -> Bool in
return first.origin.x < second.origin.x
})
// Get the first rectangle height that overlaps with the text region's height.
var textDrawingRegion = NSZeroRect
var subviewFrame = NSZeroRect // Will be needed later to set remainingRect pointer.
var precedingRegion: NSRect = NSZeroRect // Hold the view frame preceding our insertion point.
for frame in sortedFrames {
if region.origin.y + NSHeight(region) >= frame.origin.y &&
region.origin.y <= frame.origin.y + NSHeight(frame)
{
if region.origin.x <= frame.origin.x {
// Calculate the distance between the preceding subview and the approaching one.
var width: CGFloat = frame.origin.x - precedingRegion.origin.x - NSWidth(precedingRegion)
textDrawingRegion.origin = region.origin
textDrawingRegion.size = NSMakeSize(width, NSHeight(region))
subviewFrame = frame
break
}
else {
precedingRegion = frame
}
}
}
if textDrawingRegion == NSZeroRect {
return region
}
else {
// Set the "break" in the text to the subview's location:
let nextRect = NSMakeRect(subviewFrame.origin.x + NSWidth(subviewFrame),
region.origin.y,
NSWidth(proposedRect),
NSHeight(proposedRect))
remainingRect.initialize(nextRect)
return textDrawingRegion
}
}
}

Resources