How to have a button which is projected outside the NSView? - objective-c

I want to implement a button which is projected outside the NSView. The similar button is shown in below image.

If you have to have a view overflowing the window, you can't do that using the masksToBound property of CALayer. You need to use a child borderless window and position it correctly.
Here's an example (in ObjC):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSRect windowFrame = self.window.frame;
NSRect childWindowFrame = {
.origin.x = CGRectGetMidX(windowFrame) - 25,
.origin.y = CGRectGetMinY(windowFrame) - 25,
.size.width = 50,
.size.height = 50,
};
NSWindow *childWindow = [[NSWindow alloc] initWithContentRect:childWindowFrame
styleMask:NSWindowStyleMaskBorderless
backing:NSBackingStoreBuffered
defer:YES];
childWindow.backgroundColor = [NSColor clearColor];
childWindow.contentView.wantsLayer = YES;
childWindow.contentView.layer.backgroundColor = [NSColor redColor].CGColor;
childWindow.contentView.layer.cornerRadius = 25.0;
[self.window addChildWindow:childWindow ordered:NSWindowAbove];
}
The red circle in this screenshot is the child window.

You can do this by making sure your parent view is layer backed and setting the masksToBounds property of its layer to false.
Here's an AppKit playground showing this in action:
//: A Cocoa based Playground to present user interface
import AppKit
import PlaygroundSupport
let nibFile = NSNib.Name("MyView")
var topLevelObjects : NSArray?
Bundle.main.loadNibNamed(nibFile, owner:nil, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView }
let rootView = views.first as! NSView
// create parent
let parentView = NSView()
rootView.addSubview(parentView)
parentView.wantsLayer = true
parentView.layer?.masksToBounds = false
parentView.layer?.backgroundColor = NSColor.red.cgColor
parentView.translatesAutoresizingMaskIntoConstraints = false
parentView.centerXAnchor.constraint(equalTo: rootView.centerXAnchor).isActive = true
parentView.centerYAnchor.constraint(equalTo: rootView.centerYAnchor).isActive = true
parentView.widthAnchor.constraint(equalToConstant: 200).isActive = true
parentView.heightAnchor.constraint(equalToConstant: 200).isActive = true
let childView = NSView()
parentView.addSubview(childView)
childView.wantsLayer = true
childView.layer?.backgroundColor = NSColor.green.cgColor
childView.translatesAutoresizingMaskIntoConstraints = false
childView.centerXAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
childView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).isActive = true
childView.widthAnchor.constraint(equalToConstant: 100).isActive = true
childView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// Present the view in Playground
PlaygroundPage.current.liveView = views[0] as! NSView
This will draw:
The green view is a child of the red view.

Related

Swift 3 Initializing buttons

Where does one put button initialization code for controls. It won't work in ViewController.swift correct? because I need it to init immediately, not after a button is pressed.
For example I want rounded corners on the buttons.
#IBAction func buttonAdd(_ sender: AnyObject) {
let button = buttonAdd!
button.backgroundColor = UIColor.cyan
// button.backgroundColor = .clear
button.layer.cornerRadius = 15
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
}
However the this let statement won't work outside of the function either.
Do I need to put init code in a function?
Where would I call the function from?
'buttonAdd' is a function, not a button.
try this code.
if let button = sender as? UIButton
{
button.backgroundColor = UIColor.cyan
// button.backgroundColor = .clear
button.layer.cornerRadius = 15
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
}
So, you want to init the button with roundedCorner and border.
There's a 'viewDidLoad' function that called only once in view controller's life cycle.
If you want to do something only once, do it in 'viewDidLoad' function.
override func viewDidLoad()
{
super.viewDidLoad()
let btn = UIButton(type: .custom)
btn.frame = .init(x: 100, y: 100, width: 100, height: 30)
btn.setTitle("Hello", for: .normal)
btn.backgroundColor = UIColor.cyan
btn.layer.cornerRadius = 15
btn.layer.borderColor = UIColor.black.cgColor
btn.layer.borderWidth = 1
// you must call this for rounded corner
btn.layer.masksToBounds = true
self.view.addSubview(btn)
}
If you created your button in Interface Builder, You can do it like this.
#IBOutlet weak var btn: UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
btn.layer.cornerRadius = 15
btn.layer.borderColor = UIColor.black.cgColor
btn.layer.borderWidth = 1
// you must call this for rounded corner
btn.layer.masksToBounds = true
}
I found the answer to my question...You would put button init code in ViewController.Swift...in the following function created automatically by the View Controller and you must add button Outlet in addition to button Action.
override func viewDidLoad() {
super.viewDidLoad()
let button = buttonAdd!
button.backgroundColor = UIColor.cyan
// button.backgroundColor = .clear
button.layer.cornerRadius = 10
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor

SKShapeNode Positioning

I'm poking around SpriteKit and encountered some weirdness
i.e i'm simply adding rectangular SKShapeNodethat should be fullscreen on iPhone
override func didMoveToView(view: SKView) {
let _box = SKShapeNode(rectOfSize: self.frame.size)
_box.strokeColor = SKColor.blueColor()
_box.fillColor = SKColor.blueColor()
_box.position = CGPointMake(200, 200)
_box.name = "box"
self.addChild(_box)
let _player = SKShapeNode(circleOfRadius: 50)
_player.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
_player.fillColor = SKColor.redColor()
_player.strokeColor = SKColor.redColor()
_player.name = "player"
_player.physicsBody = SKPhysicsBody(circleOfRadius: 50)
_player.physicsBody?.dynamic = true
_player.physicsBody?.allowsRotation = false
self.addChild(_player)
let _ground = SKShapeNode(rectOfSize: CGSizeMake(self.frame.width, 20))
_ground.name = "ground"
_ground.position = CGPointMake(0, 0)
_ground.fillColor = SKColor.greenColor()
_ground.strokeColor = SKColor.greenColor()
_ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.width, 20))
_ground.physicsBody?.dynamic = false
self.addChild(_ground)
}
My views are initialised this way
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
The resulting scene looks like so: screenshot
All Nodes are shifted to the left-bottom.
Basically left-bottom corner of rectangular shapes is out of bounds (i've checked that by positioning blue shape to (200, 200) instead of (0, 0) - and left-bottome edges were still out of screen frame.
Anchor point for scene is set to (0, 0) so the basically it looks like for me, that it sets position for central point of shape.
What is a best way to define position of left-bottom corner of the Node, instead of mid-point?
_box.position = CGPointMake(self.size.width*.5, self.size.height*.5)
This will position the center of your sprite at the center of the screen.
If you create a shape using a bezier path or CGPath, you can specify the position of its "anchor point," where the path's origin specifies the bottom/left corner of the path. For example,
UIBezierPath* bezierPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, 50, 50)];
SKShapeNode *shape = [SKShapeNode node];
shape.path = bezierPath.CGPath;
shape.fillColor = [SKColor blueColor];
Also, if your deployment target is iOS 8, you can create your shape node with
SKShapeNode *shape = [SKShapeNode shapeNodeWithRect:CGRectMake(0, 0, 50, 50)];
shape.fillColor = [SKColor blueColor];

how to add new SKScene With in SKScene in SpritKit Swift

I have to add next SKScene in SpritKit in swift
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!)
{
var objgamescen = GameScene()
//objgamescen.initWithSize(self.size, playerWon: true)
self.view .presentScene(objgamescen)
}
Then GameScene is Blanked and Self.frame is (0.0,0.0,1.0,1.0)
Multiple Scene Added in SKScene By This:
let skView = self.view as SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
var scene: HomeScene!
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
if UIScreen.mainScreen().bounds.size.width < 568 {
scene = HomeScene(size: CGSizeMake(480, 320))
}else {
scene = HomeScene(size: CGSizeMake(568, 320))
}
}else {
scene = HomeScene(size: CGSizeMake(1024, 768))
}
scene.scaleMode = .AspectFill
skView.presentScene(scene)
It sounds like you are just trying to restart the scene, you can do that by creating a function like this
func goToGameScene(){
let gameScene:GameScene = GameScene(size: self.view!.bounds.size) // create your new scene
let transition = SKTransition.fadeWithDuration(1.0) // create type of transition (you can check in documentation for more transtions)
gameScene.scaleMode = SKSceneScaleMode.Fill
self.view!.presentScene(gameScene, transition: transition)
}
Then you just call it where you want it to take place
goToGameScene()

UIView set only side borders

Is there a way to set the sides of the border of a UIView to one color and leave the top and the bottom another?
Nope—CALayer borders don’t support that behavior. The easiest way to accomplish what you want is adding an n-point-wide opaque subview with your desired border color as its background color on each side of your view.
Example:
CGSize mainViewSize = theView.bounds.size;
CGFloat borderWidth = 2;
UIColor *borderColor = [UIColor redColor];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, borderWidth, mainViewSize.height)];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(mainViewSize.width - borderWidth, 0, borderWidth, mainViewSize.height)];
leftView.opaque = YES;
rightView.opaque = YES;
leftView.backgroundColor = borderColor;
rightView.backgroundColor = borderColor;
// for bonus points, set the views' autoresizing mask so they'll stay with the edges:
leftView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin;
rightView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin;
[theView addSubview:leftView];
[theView addSubview:rightView];
[leftView release];
[rightView release];
Note that this won’t quite match the behavior of CALayer borders—the left and right border views will always be inside the boundaries of their superview.
The answer with the views that works like borders are very nice, but remember that every view is a UI Object that cost lots of memory.
I whould use uivew's layer to paint a stroke with color on an already existing UIview.
-(CAShapeLayer*)drawLineFromPoint:(CGPoint)fromPoint toPoint:(CGPoint) toPoint withColor:(UIColor *)color andLineWidth:(CGFloat)lineWidth{
CAShapeLayer *lineShape = nil;
CGMutablePathRef linePath = nil;
linePath = CGPathCreateMutable();
lineShape = [CAShapeLayer layer];
lineShape.lineWidth = lineWidth;
lineShape.strokeColor = color.CGColor;
NSUInteger x = fromPoint.x;
NSUInteger y = fromPoint.y;
NSUInteger toX = toPoint.x;
NSUInteger toY = toPoint.y;
CGPathMoveToPoint(linePath, nil, x, y);
CGPathAddLineToPoint(linePath, nil, toX, toY);
lineShape.path = linePath;
CGPathRelease(linePath);
return lineShape;}
and add it to our view.
CAShapeLayer* borderLine=[self drawLineFromPoint:CGPointMake(0, 0) toPoint:CGPointMake(0,_myView.frame.size.height) withColor:[UIColor lightGrayColor] andLineWidth:1.0f];
[_myView.layer addSublayer:borderLine];
So... We take a point and actually painting a line from top to the bottom of our view. The result is that there is a line that looks like a one pixel width border.
Updated for Swift 3.0
I wrote a Swift extension (for a UIButton) that simulates setting a border on any side of a UIView to a given color and width. It's similar to #Noah Witherspoon's approach, but self-contained and autolayout constraint based.
// Swift 3.0
extension UIView {
enum Border {
case left
case right
case top
case bottom
}
func setBorder(border: UIView.Border, weight: CGFloat, color: UIColor ) {
let lineView = UIView()
addSubview(lineView)
lineView.backgroundColor = color
lineView.translatesAutoresizingMaskIntoConstraints = false
switch border {
case .left:
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .right:
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .top:
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
case .bottom:
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
}
}
}
This sounds like one of two answers:
If your view is a static size, then just put a UIView behind it that is 2 pixels wider and 2 pixels shorter than your front view.
If it is non-static sized then you could do the same, resizing the backing view whenever your foreground view is resized, or implement a custom object that implements a UIView, and implement (override) your own drawRect routine.
NAUIViewWithBorders did the trick for me. See also the creator's SO post here. Worth checking out if you need this functionality for more than a couple views.
public extension UIView {
// Border type and arbitrary tag values to identify UIView borders as subviews
public enum BorderType: Int {
case left = 20000
case right = 20001
case top = 20002
case bottom = 20003
}
public func addBorder(borderType: BorderType, width: CGFloat, color: UIColor) {
// figure out frame and resizing based on border type
var autoresizingMask: UIViewAutoresizing
var layerFrame: CGRect
switch borderType {
case .left:
layerFrame = CGRect(x: 0, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleRightMargin ]
case .right:
layerFrame = CGRect(x: self.bounds.width - width, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleLeftMargin ]
case .top:
layerFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleBottomMargin ]
case .bottom:
layerFrame = CGRect(x: 0, y: self.bounds.height - width, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleTopMargin ]
}
// look for the existing border in subviews
var newView: UIView?
for eachSubview in self.subviews {
if eachSubview.tag == borderType.rawValue {
newView = eachSubview
break
}
}
// set properties on existing view, or create a new one
if newView == nil {
newView = UIView(frame: layerFrame)
newView?.tag = borderType.rawValue
self.addSubview(newView!)
} else {
newView?.frame = layerFrame
}
newView?.backgroundColor = color
newView?.autoresizingMask = autoresizingMask
}

setFrame not working with a subview

I am implementing a sliding drawer on iOS5 (iPad). I have created the drawer by subclassing UIView. The drawer is added to the main view, which works fine. However, when I try to slide the drawer on/off screen using a swipe gesture and setFrame, the drawer does not move.
I believe I have implemented the gesture recognizer correctly, and the frame is also being set correctly. However, the drawer just does not move. Any thoughts on what I am doing wrong?
Below is my code:
The following method is called from viewDidLoad from my controller:
- (void)loadVerticalDrawer
{
NSLog(#"LoadVerticalDrawer Executed");
verticalDrawerHidden = YES;
if (verticalDrawerHidden) {
verticalDrawer = [[VerticalDrawer alloc] initWithFrame:CGRectMake(514, 250, 60, 248)];//adjust verticalDrawer height and width here;
} else {
verticalDrawer = [[VerticalDrawer alloc] initWithFrame:CGRectMake(464, 250, 60, 248)];//adjust verticalDrawer height and width here;
}
verticalDrawer.appsManager = appsManager;
verticalDrawer.delegate = self;
[self.view addSubview:verticalDrawer];
}
The following is also called from viewDidLoad:
rightDrawerLeftSwipe = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightDrawerHandleSwipeLeft:)] autorelease];
rightDrawerLeftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
rightDrawerLeftSwipe.numberOfTouchesRequired = 1;
rightDrawerLeftSwipe.delegate = self;
[verticalDrawer addGestureRecognizer:rightDrawerLeftSwipe];
rightDrawerRightSwipe = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightDrawerHandleSwipeRight:)] autorelease];
rightDrawerRightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
rightDrawerRightSwipe.numberOfTouchesRequired = 1;
rightDrawerRightSwipe.delegate = self;
[verticalDrawer addGestureRecognizer:rightDrawerRightSwipe];
FInally, this is the handler for the Right Swipe:
-(void) rightDrawerHandleSwipeRight:(UISwipeGestureRecognizer*) recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
if (!verticalDrawerHidden){
verticalDrawerHidden = YES;
float x = verticalDrawer.frame.origin.x;
float y = verticalDrawer.frame.origin.y;
float width = verticalDrawer.frame.size.width;
float height = verticalDrawer.frame.size.height;
NSLog(#"Swipe left, Vertical drawer, x=%f, y=%f, width=%f, height=%f:", x,y,width,height);
x+=50;
[verticalDrawer setFrame:CGRectMake(x,y,width,height)];
NSLog(#"Swipe left, Vertical drawer, x=%f, y=%f, width=%f, height=%f:", x,y,width,height);
return;
}
else {
return;
}
}
}
Please note that the frame of verticalDrawer is being set correctly (and the swipe handler is being called as desired), as per the logs, its just that the view is not moving at all!!