Hello I'm creating a function to crop the photo, I would like the outside of the container that cropping the photo is black with an opacity of 0.5, look at the picture below to better understand:
All I drew in black is what I want it to be black with an opacity of 0.5, how to do this?
The user can move and change the size of frame, I use for this size change CGRect. So it is necessary that the black background is dynamic with the change of the size and the displacement of the frame, I do not know how to do it
The class of my UIIvire frame:
class ResizableView: UIView {
enum Edge {
case topLeft, topRight, bottomLeft, bottomRight, none
}
static var edgeSize: CGFloat = 44.0
private typealias `Self` = ResizableView
var currentEdge: Edge = .none
var touchStart = CGPoint.zero
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
touchStart = touch.location(in: self)
currentEdge = {
if self.bounds.size.width - touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomRight
} else if touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topLeft
} else if self.bounds.size.width - touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topRight
} else if touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomLeft
}
return .none
}()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: self)
let previous = touch.previousLocation(in: self)
let originX = self.frame.origin.x
let originY = self.frame.origin.y
let width = self.frame.size.width
let height = self.frame.size.height
let deltaWidth = currentPoint.x - previous.x
let deltaHeight = currentPoint.y - previous.y
switch currentEdge {
case .topLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY + deltaHeight, width: width - deltaWidth, height: height - deltaHeight)
case .topRight:
self.frame = CGRect(x: originX, y: originY + deltaHeight, width: width + deltaWidth, height: height - deltaHeight)
case .bottomRight:
self.frame = CGRect(x: originX, y: originY, width: currentPoint.x + deltaWidth, height: currentPoint.y + deltaWidth)
case .bottomLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY, width: width - deltaWidth, height: height + deltaHeight)
default:
// Moving
self.center = CGPoint(x: self.center.x + currentPoint.x - touchStart.x,
y: self.center.y + currentPoint.y - touchStart.y)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
currentEdge = .none
}
// MARK: - Border
#IBInspectable public var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable public var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
}
Use the maskView to insert an image which covers the bleed area. The image should be 50% opacity in the bleed area. Here's an example on how to implement this technique.
let imageView = UIImageView(image: UIImage(named: "star"))
imageView.contentMode = .scaleAspectFill
view.maskView = imageView
Related
I am trying to crop a selected portion of NSImage which is fitted as per ProportionallyUpOrDown(AspectFill) Mode.
I am drawing a frame using mouse dragged event like this:
class CropImageView: NSImageView {
var startPoint: NSPoint!
var shapeLayer: CAShapeLayer!
var flagCheck = false
var finalPoint: NSPoint!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
override var image: NSImage? {
set {
self.layer = CALayer()
self.layer?.contentsGravity = kCAGravityResizeAspectFill
self.layer?.contents = newValue
self.wantsLayer = true
super.image = newValue
}
get {
return super.image
}
}
override func mouseDown(with event: NSEvent) {
self.startPoint = self.convert(event.locationInWindow, from: nil)
if self.shapeLayer != nil {
self.shapeLayer.removeFromSuperlayer()
self.shapeLayer = nil
}
self.flagCheck = true
var pixelColor: NSColor = NSReadPixel(startPoint) ?? NSColor()
shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = NSColor.clear.cgColor
if pixelColor == NSColor.black {
pixelColor = NSColor.color_white
} else {
pixelColor = NSColor.black
}
shapeLayer.strokeColor = pixelColor.cgColor
shapeLayer.lineDashPattern = [1]
self.layer?.addSublayer(shapeLayer)
var dashAnimation = CABasicAnimation()
dashAnimation = CABasicAnimation(keyPath: "lineDashPhase")
dashAnimation.duration = 0.75
dashAnimation.fromValue = 0.0
dashAnimation.toValue = 15.0
dashAnimation.repeatCount = 0.0
shapeLayer.add(dashAnimation, forKey: "linePhase")
}
override func mouseDragged(with event: NSEvent) {
let point: NSPoint = self.convert(event.locationInWindow, from: nil)
var newPoint: CGPoint = self.startPoint
let xDiff = point.x - self.startPoint.x
let yDiff = point.y - self.startPoint.y
let dist = min(abs(xDiff), abs(yDiff))
newPoint.x += xDiff > 0 ? dist : -dist
newPoint.y += yDiff > 0 ? dist : -dist
let path = CGMutablePath()
path.move(to: self.startPoint)
path.addLine(to: NSPoint(x: self.startPoint.x, y: newPoint.y))
path.addLine(to: newPoint)
path.addLine(to: NSPoint(x: newPoint.x, y: self.startPoint.y))
path.closeSubpath()
self.shapeLayer.path = path
}
override func mouseUp(with event: NSEvent) {
self.finalPoint = self.convert(event.locationInWindow, from: nil)
}
}
and selected this area as shown in picture using black dotted line:
My Cropping Code logic is this:
// resize Image Methods
extension CropProfileView {
func resizeImage(image: NSImage) -> Data {
var scalingFactor: CGFloat = 0.0
if image.size.width >= image.size.height {
scalingFactor = image.size.width/cropImgView.size.width
} else {
scalingFactor = image.size.height/cropImgView.size.height
}
let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingFactor
let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingFactor
let xPos = ((image.size.width/2) - (cropImgView.bounds.midX - self.cropImgView.startPoint.x) * scalingFactor)
let yPos = ((image.size.height/2) - (cropImgView.bounds.midY - (cropImgView.size.height - self.cropImgView.startPoint.y)) * scalingFactor)
var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)
let imageRef = image.cgImage(forProposedRect: &croppedRect, context: nil, hints: nil)
guard let croppedImage = imageRef?.cropping(to: croppedRect) else {return Data()}
let imageWithNewSize = NSImage(cgImage: croppedImage, size: NSSize(width: width, height: height))
guard let data = imageWithNewSize.tiffRepresentation,
let rep = NSBitmapImageRep(data: data),
let imgData = rep.representation(using: .png, properties: [.compressionFactor: NSNumber(floatLiteral: 0.25)]) else {
return imageWithNewSize.tiffRepresentation ?? Data()
}
return imgData
}
}
With this cropping logic i am getting this output:
I think as image is AspectFill thats why its not getting cropped in perfect size as per selected frame. Here if you look at output: xpositon & width & heights are not perfect. Or probably i am not calculating these co-ordinates properly. Let me know the faults probably i am calculating someting wrong.
Note: the CropImageView class in the question is a subclass of NSImageView but the view is layer-hosting and the image is drawn by the layer, not by NSImageView. imageScaling is not used.
When deciding which scaling factor to use you have to take the size of the image view into account. If the image size is width:120, height:100 and the image view size is width:120, height 80 then image.size.width >= image.size.height is true and image.size.width/cropImgView.size.width is 1 but the image is scaled because image.size.height/cropImgView.size.height is 1.25. Calculate the horizontal and vertical scaling factors and use the largest.
See How to crop a UIImageView to a new UIImage in 'aspect fill' mode?
Here's the calculation of croppedRect assuming cropImgView.size returns self.layer!.bounds.size.
var scalingWidthFactor: CGFloat = image.size.width/cropImgView.size.width
var scalingHeightFactor: CGFloat = image.size.height/cropImgView.size.height
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
switch cropImgView.layer?.contentsGravity {
case CALayerContentsGravity.resize: break
case CALayerContentsGravity.resizeAspect:
if scalingWidthFactor > scalingHeightFactor {
scalingHeightFactor = scalingWidthFactor
yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
}
else {
scalingWidthFactor = scalingHeightFactor
xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
}
case CALayerContentsGravity.resizeAspectFill:
if scalingWidthFactor < scalingHeightFactor {
scalingHeightFactor = scalingWidthFactor
yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
}
else {
scalingWidthFactor = scalingHeightFactor
xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
}
default:
print("contentsGravity \(String(describing: cropImgView.layer?.contentsGravity)) is not supported")
return nil
}
let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingWidthFactor
let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingHeightFactor
let xPos = (self.cropImgView.startPoint.x - xOffset) * scalingWidthFactor
let yPos = (cropImgView.size.height - self.cropImgView.startPoint.y - yOffset) * scalingHeightFactor
var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)
Bugfix: cropImgView.finalPoint should be the corner of the selection, not the location of mouseUp. In CropImageView set self.finalPoint = newPoint in mouseDragged instead of mouseUp.
I'm working in SpriteKit and I need to create a 'playable area', an area that is same size as the device screen so that I can stop my player from moving off-screen.
I'm using the following line of code:
var playableRect: CGRect = UIScreen.main.bounds
But the resulting rectangle is about a quarter of the device screen, with a corner of that rectangle at what looks to be the center of the screen. And the device orientation doesn't change that.
I've tried everything I can think of. Nothing is working.
How do I create a rectangle that's the same size as the device screen?
Here's my full coding, after changing to include Gene's suggestion. I incorporated Gene's suggestion by revising playableRect inside the didMove method. But the result is unchanged with that coding.
import SpriteKit
import GameplayKit
import CoreMotion
#objcMembers
class GameSceneUsingTilt: SKScene {
let player = SKSpriteNode(imageNamed: "player-motorbike")
let motionManager = CMMotionManager()
var playableRect = UIScreen.main.bounds
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "road")
background.zPosition = -1
addChild(background)
if let particles = SKEmitterNode(fileNamed: "Mud") {
let farRightPt = frame.maxX // start the emitter at the far right x-point of the view
particles.advanceSimulationTime(10)
particles.position.x = farRightPt
addChild(particles)
}
let nearLeftPt = frame.minX * 3 / 4 // start the player at a quarter of the way to the far left x-point of the view
player.position.x = nearLeftPt
player.zPosition = 1
addChild(player)
motionManager.startAccelerometerUpdates()
// coding below shows outline of playableRect
let bounds = UIScreen.main.bounds
let scale = UIScreen.main.scale
// let size = CGSize(width: bounds.size.width * scale, height: bounds.size.height * scale)
playableRect = CGRect(x: 0, y: 0, width: bounds.size.width * scale, height: bounds.size.height * scale)
drawPlayableRect(rect: playableRect)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
func boundsCheck() {
let playableRect = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: frame.height)
let bottomLeft = CGPoint(x: playableRect.minX, y: playableRect.minY)
let topRight = CGPoint(x: playableRect.maxX, y: playableRect.maxY)
if player.position.x <= bottomLeft.x {
player.position.x = bottomLeft.x
}
if player.position.x >= topRight.x {
player.position.x = topRight.x
}
if player.position.y <= bottomLeft.y {
player.position.y = bottomLeft.y
}
if player.position.y >= topRight.y {
player.position.y = topRight.y
}
}
override func update(_ currentTime: TimeInterval) {
if let accelerometerData = motionManager.accelerometerData {
// note that since the game will be running in landscape mode, up and down movement is controlled by the x axis and right to left movement is controlled by the y-axis...so this is the inverse of what you'd normally think.
let changeX = CGFloat(accelerometerData.acceleration.y) * 4
let changeY = CGFloat(accelerometerData.acceleration.x) * 4
// you have to subtract from x position and add to the y position because device is rotated so the axises aren't what you would normally expect.
player.position.x -= changeX
player.position.y += changeY
// check to make sure position isn't outside payable area:
boundsCheck()
}
}
// function below shows outline of playableRect
func drawPlayableRect(rect: CGRect) {
let shape = SKShapeNode()
let path = CGMutablePath()
path.addRect(rect)
shape.path = path
shape.strokeColor = .red
shape.lineWidth = 4.0
addChild(shape)
}
}
Use: UIScreen.main.nativeBounds
I'm trying to make an app based on image editing and I'm founding some problems coding a resizing by corners dragging crop area.
I have croppingView.swift where the crop rectangle is drawn when launched:
override func viewDidAppear(_ animated: Bool) {
let imagePoint = CGPoint(x: contentView.center.x, y: contentView.center.y)
let imageSize = CGSize(width: cropImageView.image!.size.width, height: cropImageView.image!.size.height)
let widthScale = cropImageView.frame.width / imageSize.width
let heightScale = cropImageView.frame.height / imageSize.height
let scale = min(widthScale, heightScale)
let width = imageSize.width * scale
let height = imageSize.height * scale
let size = CGSize(width: width, height: height)
let originViewFrame = CGPoint(x: imagePoint.x - width/2.0, y: imagePoint.y - height/2.0)
let cropSize = CGSize(width: width * 0.7, height: height * 0.7)
let cropViewFrame = CGRect(origin: originViewFrame, size: cropSize)
cropView = ShapeView(frame: cropViewFrame)
cropView.backgroundColor = UIColor(red: 224.0/255.0, green: 224.0/255.0, blue: 224.0/255.0, alpha: 0.3)
cropView.layer.borderColor = UIColor(red: 97.0/255.0, green: 97.0/255.0, blue: 97.0/255.0, alpha: 1.0).cgColor
cropView.layer.borderWidth = 1.0
cropView.center = imagePoint
self.view.addSubview(cropView)
}
The ShapeView.swift to draw the cropping area is as follows:
class ShapeView: UIView {
var previousLocation = CGPoint.zero
var topLeft = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
strokeColor: UIColor.white)
var topRight = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
strokeColor: UIColor.white)
var bottomLeft = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
strokeColor: UIColor.white)
var bottomRight = DragHandle(fillColor:UIColor(red: 0.0/255.0, green: 150.0/255.0, blue: 136.0/255.0, alpha: 1.0),
strokeColor: UIColor.white)
override func didMoveToSuperview() {
superview?.addSubview(topLeft)
superview?.addSubview(topRight)
superview?.addSubview(bottomLeft)
superview?.addSubview(bottomRight)
var pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
topLeft.addGestureRecognizer(pan)
pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
topRight.addGestureRecognizer(pan)
pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
bottomLeft.addGestureRecognizer(pan)
pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
bottomRight.addGestureRecognizer(pan)
pan = UIPanGestureRecognizer(target: self, action: #selector(handleMove))
self.addGestureRecognizer(pan)
self.updateDragHandles()
}
func updateDragHandles() {
topLeft.center = self.transformedTopLeft()
topRight.center = self.transformedTopRight()
bottomLeft.center = self.transformedBottomLeft()
bottomRight.center = self.transformedBottomRight()
}
//Gesture Methods
#IBAction func handleMove(gesture:UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.superview!)
var center = self.center
center.x += translation.x
center.y += translation.y
self.center = center
gesture.setTranslation(CGPoint.zero, in: self.superview!)
updateDragHandles()
}
#IBAction func handlePan(gesture:UIPanGestureRecognizer) {
let translation = gesture.translation(in: self)
switch gesture.view! {
case topLeft:
if gesture.state == .began {
self.setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 1))
}
self.bounds.size.width -= translation.x
self.bounds.size.height -= translation.y
case topRight:
if gesture.state == .began {
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 1))
}
self.bounds.size.width += translation.x
self.bounds.size.height -= translation.y
case bottomLeft:
if gesture.state == .began {
self.setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 0))
}
self.bounds.size.width -= translation.x
self.bounds.size.height += translation.y
case bottomRight:
if gesture.state == .began {
self.setAnchorPoint(anchorPoint: CGPoint.zero)
}
self.bounds.size.width += translation.x
self.bounds.size.height += translation.y
default:()
}
gesture.setTranslation(CGPoint.zero, in: self)
updateDragHandles()
if gesture.state == .ended {
self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
}
}
}
As you can see I'm adding one circle in each corner of the rectangle. These circles are used to resize the cropping area by dragging. Here the key is the updateDragHandles()function which updates the positions of all other circle keeping them on the corners.
So when launching the cropping area appears like:
And when user moves for example the bottom-right corner by dragging everything is working fine:
Now the problem is when I click on the portrait button to change cropping area orientation. With the next code into croppingView.swift the grey rectangle correctly changes the size but the circles remain on the their position:
#IBAction func portButton(_ sender: Any) {
portButton.tintColor = UIColor.systemBlue
landButton.tintColor = UIColor.systemGray
isPortait = 1
let inWidth = cropView.frame.size.width
let inHeight = cropView.frame.size.height
if inWidth > inHeight {
let scaleW = cropView.frame.size.height / cropView.frame.size.width
let scaleH = cropView.frame.size.width / cropView.frame.size.height
let fixPoint = CGPoint(x: 0.5, y: 0.5)
cropView.setAnchorPoint(anchorPoint: fixPoint)
cropView.transform = cropView.transform.scaledBy(x: scaleW, y: scaleH)
let vc = ShapeView()
vc.updateDragHandles()
}
}
It's like updateDragHandles is not working from this ViewController...
Probably it's a basic mistake but I can't find out the solution.
Any suggestion?
Thank you in advance!
DragHandle.swift
let diameter:CGFloat = 30
import UIKit
class DragHandle: UIView {
var fillColor = UIColor.darkGray
var strokeColor = UIColor.lightGray
var strokeWidth: CGFloat = 1.0
required init(coder aDecoder: NSCoder) {
fatalError("Use init(fillColor:, strokeColor:)")
}
init(fillColor: UIColor, strokeColor: UIColor, strokeWidth width: CGFloat = 1.0) {
super.init(frame: CGRect(x: 0, y: 0, width: diameter, height: diameter))
self.fillColor = fillColor
self.strokeColor = strokeColor
self.strokeWidth = width
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect)
{
super.draw(rect)
let handlePath = UIBezierPath(ovalIn: rect.insetBy(dx: 10 + strokeWidth, dy: 10 + strokeWidth))
fillColor.setFill()
handlePath.fill()
strokeColor.setStroke()
handlePath.lineWidth = strokeWidth
handlePath.stroke()
}
}
UIViewExtensions.swift
import Foundation
import UIKit
extension UIView {
func offsetPointToParentCoordinates(point: CGPoint) -> CGPoint {
return CGPoint(x: point.x + self.center.x, y: point.y + self.center.y)
}
func pointInViewCenterTerms(point:CGPoint) -> CGPoint {
return CGPoint(x: point.x - self.center.x, y: point.y - self.center.y)
}
func pointInTransformedView(point: CGPoint) -> CGPoint {
let offsetItem = self.pointInViewCenterTerms(point: point)
let updatedItem = offsetItem.applying(self.transform)
let finalItem = self.offsetPointToParentCoordinates(point: updatedItem)
return finalItem
}
func originalFrame() -> CGRect {
let currentTransform = self.transform
self.transform = .identity
let originalFrame = self.frame
self.transform = currentTransform
return originalFrame
}
func transformedTopLeft() -> CGPoint {
let frame = self.originalFrame()
let point = frame.origin
return self.pointInTransformedView(point: point)
}
func transformedTopRight() -> CGPoint {
let frame = self.originalFrame()
var point = frame.origin
point.x += frame.size.width
return self.pointInTransformedView(point: point)
}
func transformedBottomRight() -> CGPoint {
let frame = self.originalFrame()
var point = frame.origin
point.x += frame.size.width
point.y += frame.size.height
return self.pointInTransformedView(point: point)
}
func transformedBottomLeft() -> CGPoint {
let frame = self.originalFrame()
var point = frame.origin
point.y += frame.size.height
return self.pointInTransformedView(point: point)
}
func setAnchorPoint(anchorPoint: CGPoint) {
var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x: self.bounds.size.width * self.layer.anchorPoint.x, y: self.bounds.size.height * self.layer.anchorPoint.y)
newPoint = newPoint.applying(self.transform)
oldPoint = oldPoint.applying(self.transform)
var position = self.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
self.layer.position = position
self.layer.anchorPoint = anchorPoint
}
}
I'm using the Swift Chart. I'd like to modify it to allow the user to select a range. The idea is to touch, swipe left/right, and then lift your finger. This should highlight the area swiped and provide a way to get the beginning and ending values of the swipe. I expect I'll need to modify the touchesBegan() and touchesEnded() events, but I don't know how.
Here's what I did to make this work:
I added range selection variables to the class
// Range selection
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
I modified touchesBegan()
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
And added a routine to touchesEnded()
handleRangeTouchesEnded(touches, event: event)
Here's the full code:
// Chart.swift
//
// Created by Giampaolo Bellavite on 07/11/14.
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
import UIKit
public protocol ChartDelegate: class {
func didTouchChart(_ chart: Chart, indexes: [Int?], x: Float, left: CGFloat)
func didFinishTouchingChart(_ chart: Chart)
func didEndTouchingChart(_ chart: Chart)
}
typealias ChartPoint = (x: Float, y: Float)
public enum ChartLabelOrientation {
case horizontal
case vertical
}
#IBDesignable open class Chart: UIControl {
#IBInspectable
open var identifier: String?
open var series: [ChartSeries] = [] {
didSet {
setNeedsDisplay()
}
}
open var xLabels: [Float]?
open var xLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var xLabelsTextAlignment: NSTextAlignment = .left
open var xLabelsOrientation: ChartLabelOrientation = .horizontal
open var xLabelsSkipLast: Bool = true
open var xLabelsSkipAll: Bool = true
open var yLabels: [Float]?
open var yLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var yLabelsOnRightSide: Bool = false
open var labelFont: UIFont? = UIFont.systemFont(ofSize: 12)
#IBInspectable
open var labelColor: UIColor = UIColor.black
#IBInspectable
open var axesColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
#IBInspectable
open var gridColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
open var showXLabelsAndGrid: Bool = true
open var showYLabelsAndGrid: Bool = true
open var bottomInset: CGFloat = 20
open var topInset: CGFloat = 20
#IBInspectable
open var lineWidth: CGFloat = 2
weak open var delegate: ChartDelegate?
open var minX: Float?
open var minY: Float?
open var maxX: Float?
open var maxY: Float?
open var highlightLineColor = UIColor.gray
open var highlightLineWidth: CGFloat = 0.5
open var areaAlphaComponent: CGFloat = 0.1
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
fileprivate var highlightShapeLayer: CAShapeLayer!
fileprivate var layerStore: [CAShapeLayer] = []
fileprivate var drawingHeight: CGFloat!
fileprivate var drawingWidth: CGFloat!
fileprivate var min: ChartPoint!
fileprivate var max: ChartPoint!
typealias ChartLineSegment = [ChartPoint]
override public init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience public init() {
self.init(frame: .zero)
commonInit()
}
private func commonInit() {
backgroundColor = UIColor.clear
contentMode = .redraw // redraw rects on bounds change
}
override open func draw(_ rect: CGRect) {
#if TARGET_INTERFACE_BUILDER
drawIBPlaceholder()
#else
drawChart()
#endif
}
open func add(_ series: ChartSeries) {
self.series.append(series)
}
open func add(_ series: [ChartSeries]) {
for s in series {
add(s)
}
}
open func removeSeriesAt(_ index: Int) {
series.remove(at: index)
}
open func removeAllSeries() {
series = []
}
open func valueForSeries(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Float? {
if dataIndex == nil { return nil }
let series = self.series[seriesIndex] as ChartSeries
return series.data[dataIndex!].y
}
fileprivate func drawIBPlaceholder() {
let placeholder = UIView(frame: self.frame)
placeholder.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1)
let label = UILabel()
label.text = "Chart"
label.font = UIFont.systemFont(ofSize: 28)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
label.sizeToFit()
label.frame.origin.x += frame.width/2 - (label.frame.width / 2)
label.frame.origin.y += frame.height/2 - (label.frame.height / 2)
placeholder.addSubview(label)
addSubview(placeholder)
}
fileprivate func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in self.series.enumerated() {
// Separate each line in multiple segments over and below the x axis
let segments = Chart.segmentLine(series.data as ChartLineSegment, zeroLevel: series.colors.zeroLevel)
segments.forEach({ segment in
let scaledXValues = scaleValuesOnXAxis( segment.map({ return $0.x }) )
let scaledYValues = scaleValuesOnYAxis( segment.map({ return $0.y }) )
if series.line {
drawLine(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
})
}
drawAxes()
if showXLabelsAndGrid && (xLabels != nil || series.count > 0) {
drawLabelsAndGridOnXAxis()
}
if showYLabelsAndGrid && (yLabels != nil || series.count > 0) {
drawLabelsAndGridOnYAxis()
}
}
fileprivate func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let yValues = series.data.map({ (point: ChartPoint) -> Float in
return point.y })
let newMinX = xValues.min()!
let newMinY = yValues.min()!
let newMaxX = xValues.max()!
let newMaxY = yValues.max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = (xLabels!).min()!
let newMaxX = (xLabels!).max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = (yLabels!).min()!
let newMaxY = (yLabels!).max()!
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
fileprivate func scaleValuesOnXAxis(_ values: [Float]) -> [Float] {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 {
factor = 0
} else {
factor = width / (max.x - min.x)
}
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
fileprivate func scaleValuesOnYAxis(_ values: [Float]) -> [Float] {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
fileprivate func scaleValueOnYAxis(_ value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
fileprivate func getZeroValueOnYAxis(zeroLevel: Float) -> Float {
if min.y > zeroLevel {
return scaleValueOnYAxis(min.y)
} else {
return scaleValueOnYAxis(zeroLevel)
}
}
fileprivate func drawLine(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let path = CGMutablePath()
path.move(to: CGPoint(x: CGFloat(xValues.first!), y: CGFloat(yValues.first!)))
for i in 1..<yValues.count {
let y = yValues[i]
path.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(y)))
}
let lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveZeroLine {
lineLayer.strokeColor = series[seriesIndex].colors.above.cgColor
} else {
lineLayer.strokeColor = series[seriesIndex].colors.below.cgColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
}
fileprivate func drawArea(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let area = CGMutablePath()
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: series[seriesIndex].colors.zeroLevel))
area.move(to: CGPoint(x: CGFloat(xValues[0]), y: zero))
for i in 0..<xValues.count {
area.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(yValues[i])))
}
area.addLine(to: CGPoint(x: CGFloat(xValues.last!), y: zero))
let areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveZeroLine {
areaLayer.fillColor = series[seriesIndex].colors.above.withAlphaComponent(areaAlphaComponent).cgColor
} else {
areaLayer.fillColor = series[seriesIndex].colors.below.withAlphaComponent(areaAlphaComponent).cgColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
fileprivate func drawAxes() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(axesColor.cgColor)
context.setLineWidth(0.5)
// horizontal axis at the bottom
context.move(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
// horizontal axis at the top
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.strokePath()
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: y))
context.strokePath()
}
// vertical axis on the left
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.strokePath()
// vertical axis on the right
context.move(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
}
fileprivate func drawLabelsAndGridOnXAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if xLabels == nil {
// Use labels from the first series
labels = series[0].data.map({ (point: ChartPoint) -> Float in
return point.x })
} else {
labels = xLabels!
}
let scaled = scaleValuesOnXAxis(labels)
let padding: CGFloat = 5
scaled.enumerated().forEach { (i, value) in
let x = CGFloat(value)
let isLastLabel = x == drawingWidth
// Add vertical grid for each label, except axes on the left and right
if x != 0 && x != drawingWidth {
context.move(to: CGPoint(x: x, y: CGFloat(0)))
if xLabelsSkipAll {
let height: CGFloat = bounds.height - 20.0
context.addLine(to: CGPoint(x: x, y: height))
} else {
context.addLine(to: CGPoint(x: x, y: bounds.height))
}
context.strokePath()
}
if (xLabelsSkipLast && isLastLabel) || xLabelsSkipAll {
// Do not add label at the most right position
return
}
// Add label
let label = UILabel(frame: CGRect(x: x, y: drawingHeight, width: 0, height: 0))
label.font = labelFont
label.text = xLabelsFormatter(i, labels[i])
label.textColor = labelColor
// Set label size
label.sizeToFit()
// Center label vertically
label.frame.origin.y += topInset
if xLabelsOrientation == .horizontal {
// Add left padding
label.frame.origin.y -= (label.frame.height - bottomInset) / 2
label.frame.origin.x += padding
// Set label's text alignment
label.frame.size.width = (drawingWidth / CGFloat(labels.count)) - padding * 2
label.textAlignment = xLabelsTextAlignment
} else {
label.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
// Adjust vertical position according to the label's height
label.frame.origin.y += label.frame.size.height / 2
// Adjust horizontal position as the series line
label.frame.origin.x = x
if xLabelsTextAlignment == .center {
// Align horizontally in series
label.frame.origin.x += ((drawingWidth / CGFloat(labels.count)) / 2) - (label.frame.size.width / 2)
} else {
// Give some space from the vertical line
label.frame.origin.x += padding
}
}
self.addSubview(label)
}
}
fileprivate func drawLabelsAndGridOnYAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if yLabels == nil {
labels = [(min.y + max.y) / 2, max.y]
if yLabelsOnRightSide || min.y != 0 {
labels.insert(min.y, at: 0)
}
} else {
labels = yLabels!
}
let scaled = scaleValuesOnYAxis(labels)
let padding: CGFloat = 5
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
scaled.enumerated().forEach { (i, value) in
let y = CGFloat(value)
// Add horizontal grid for each label, but not over axes
if y != drawingHeight + topInset && y != zero {
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: self.bounds.width, y: y))
if labels[i] != 0 {
// Horizontal grid for 0 is not dashed
context.setLineDash(phase: CGFloat(0), lengths: [CGFloat(5)])
} else {
context.setLineDash(phase: CGFloat(0), lengths: [])
}
context.strokePath()
}
let label = UILabel(frame: CGRect(x: padding, y: y, width: 0, height: 0))
label.font = labelFont
label.text = yLabelsFormatter(i, labels[i])
label.textColor = labelColor
label.sizeToFit()
if yLabelsOnRightSide {
label.frame.origin.x = drawingWidth
label.frame.origin.x -= label.frame.width + padding
}
// Labels should be placed above the horizontal grid
label.frame.origin.y -= label.frame.height
self.addSubview(label)
}
UIGraphicsEndImageContext()
}
fileprivate func drawHighlightLineFromLeftPosition(_ left: CGFloat) {
if let shapeLayer = highlightShapeLayer {
// Use line already created
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: 0))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
shapeLayer.path = path
} else {
// Create the line
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: CGFloat(0)))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path
shapeLayer.strokeColor = highlightLineColor.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = highlightLineWidth
highlightShapeLayer = shapeLayer
layer.addSublayer(shapeLayer)
layerStore.append(shapeLayer)
}
}
func handleTouchEvents(_ touches: Set<UITouch>, event: UIEvent!) {
let point = touches.first!
let left = point.location(in: self).x
let x = valueFromPointAtX(left)
if left < 0 || left > (drawingWidth as CGFloat) {
// Remove highlight line at the end of the touch event
if let shapeLayer = highlightShapeLayer {
shapeLayer.path = nil
}
delegate?.didFinishTouchingChart(self)
return
}
drawHighlightLineFromLeftPosition(left)
if delegate == nil {
return
}
var indexes: [Int?] = []
for series in self.series {
var index: Int? = nil
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let closest = Chart.findClosestInValues(xValues, forValue: x)
if closest.lowestIndex != nil && closest.highestIndex != nil {
// Consider valid only values on the right
index = closest.lowestIndex
}
indexes.append(index)
}
delegate!.didTouchChart(self, indexes: indexes, x: x, left: left)
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
delegate?.didEndTouchingChart(self)
handleRangeTouchesEnded(touches, event: event)
}
func handleRangeTouchesEnded(_ touches: Set<UITouch>, event: UIEvent!) {
rightRangePoint = touches.first!
rightRangeLocation = rightRangePoint.location(in: self).x
// Make sure left is actually to the left
if rightRangeLocation < leftRangeLocation {
let rangePoint = leftRangePoint
let rangeLocation = leftRangeLocation
leftRangePoint = rightRangePoint
leftRangeLocation = rightRangeLocation
rightRangePoint = rangePoint
rightRangeLocation = rangeLocation
}
// Highlight the range
let layer = CAShapeLayer()
let width = rightRangeLocation - leftRangeLocation
layer.path = UIBezierPath(rect: CGRect(x: leftRangeLocation, y: topInset, width: width, height: drawingHeight)).cgPath
layer.fillColor = UIColor.red.cgColor
layer.opacity = 0.3
self.layer.addSublayer(layer)
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
}
fileprivate func valueFromPointAtX(_ x: CGFloat) -> Float {
let value = ((max.x-min.x) / Float(drawingWidth)) * Float(x) + min.x
return value
}
fileprivate func valueFromPointAtY(_ y: CGFloat) -> Float {
let value = ((max.y - min.y) / Float(drawingHeight)) * Float(y) + min.y
return -value
}
fileprivate class func findClosestInValues(_ values: [Float],
forValue value: Float
) -> (
lowestValue: Float?,
highestValue: Float?,
lowestIndex: Int?,
highestIndex: Int?
) {
var lowestValue: Float?, highestValue: Float?, lowestIndex: Int?, highestIndex: Int?
values.enumerated().forEach { (i, currentValue) in
if currentValue <= value && (lowestValue == nil || lowestValue! < currentValue) {
lowestValue = currentValue
lowestIndex = i
}
if currentValue >= value && (highestValue == nil || highestValue! > currentValue) {
highestValue = currentValue
highestIndex = i
}
}
return (
lowestValue: lowestValue,
highestValue: highestValue,
lowestIndex: lowestIndex,
highestIndex: highestIndex
)
}
fileprivate class func segmentLine(_ line: ChartLineSegment, zeroLevel: Float) -> [ChartLineSegment] {
var segments: [ChartLineSegment] = []
var segment: ChartLineSegment = []
line.enumerated().forEach { (i, point) in
segment.append(point)
if i < line.count - 1 {
let nextPoint = line[i+1]
if point.y >= zeroLevel && nextPoint.y < zeroLevel || point.y < zeroLevel && nextPoint.y >= zeroLevel {
// The segment intersects zeroLevel, close the segment with the intersection point
let closingPoint = Chart.intersectionWithLevel(point, and: nextPoint, level: zeroLevel)
segment.append(closingPoint)
segments.append(segment)
// Start a new segment
segment = [closingPoint]
}
} else {
// End of the line
segments.append(segment)
}
}
return segments
}
fileprivate class func intersectionWithLevel(_ p1: ChartPoint, and p2: ChartPoint, level: Float) -> ChartPoint {
let dy1 = level - p1.y
let dy2 = level - p2.y
return (x: (p2.x * dy1 - p1.x * dy2) / (dy1 - dy2), y: level)
}
}
I want to make it so that the player can move with a joystick but cant go out of the circle. I made the joystick but I don't know how to do the other stuff. There is an example in the image below and also my code. Hope someone can help me, thanks.
class GameScene: SKScene {
var circuloPrincipal = SKSpriteNode(imageNamed: "circulo")
var circuloFondo = SKSpriteNode(imageNamed: "circuloFondo")
let base = SKSpriteNode(imageNamed: "circuloFondo")
let ball = SKSpriteNode(imageNamed: "circulo")
var stickActive:Bool = false
override func didMoveToView(view: SKView) {
/* Setup your scene here */
base.size = CGSize(width: 100, height: 100)
base.alpha = 0.3
base.zPosition = 2.0
base.position = CGPoint(x: frame.width / 2, y: frame.height / 2 - 310)
self.addChild(base)
ball.size = CGSize(width: 50, height: 50)
ball.color = circuloPrincipal.color
//ball.alpha = 0
ball.zPosition = 3.0
ball.position = base.position
self.addChild(ball)
circuloPrincipal.size = CGSize(width: 35, height: 35)
circuloPrincipal.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
self.addChild(circuloPrincipal)
circuloPrincipal.color = UIColor(red: 0.75, green: 0.75, blue: 0.75, alpha: 1.0)
circuloPrincipal.colorBlendFactor = 1.0
circuloPrincipal.zPosition = 3.0
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if (CGRectContainsPoint(ball.frame, location)) {
stickActive = true
}else {
stickActive = false
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if (stickActive == true) {
let v = CGVector(dx: location.x - base.position.x, dy: location.y - base.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat(180 / M_PI)
print(deg + 180)
let lenght:CGFloat = base.frame.size.height / 2 - 20
let xDist: CGFloat = sin(angle - 1.57079633) * lenght
let yDist: CGFloat = cos(angle - 1.57079633) * lenght
ball.position = CGPointMake(base.position.x - xDist, base.position.y + yDist)
if (CGRectContainsPoint(base.frame, location)) {
ball.position = location
}else {
ball.position = CGPointMake(base.position.x - xDist, base.position.y + yDist)
}
} // termina stickActive
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (stickActive == true) {
let move: SKAction = SKAction.moveTo(base.position, duration: 0.2)
move.timingMode = .EaseOut
ball.runAction(move)
}
}
Look into SKConstraint - you can set a constraint that a node cannot move further than a specified distance from another node. You'd set this 'other' node to the centre of the area that your player is limited to. Sprite-Kit will automatically move your node back should it go to far. As this is being in the game loop at 60fps before the node is redrawn after it's moved, you won't get any weird 'jumping' effects.
https://developer.apple.com/reference/spritekit/skconstraint
That documentation is a bit lacking compared to some of the other SK documentation. Here's and example that make a green rectangle 'follow' a yellow triangle within a specified distance:
let distanceRange = SKRange(lowerLimit: 200, upperLimit: 400)
let distanceConstraint = SKConstraint.distance(distanceRange, toNode: yellowTriangle)
greenRect.constraints = [distanceConstraint]
The constraints property of an SKNode (greenRect) is an array, so if you also want an orientation constraint (to keep greenRect facing the yellow triangle, for example), you would code something like this:
let orientRange = SKRange(lowerLimit: 0.0, upperLimit: 0.0)
let orientConstraint = SKConstraint.orientToNode(yellowTriangle, offset: orientRange)
greenRect.constraints = [orientatConstraint, distanceConstraint]
For your particular example, you might want to set the constraint as:
let distanceRange = SKRange(lowerLimit: 0, upperLimit: limitcircle.size/2)
let distanceConstraint = SKConstraint.distance(distanceRange, toPoint: limitCircle.position)
player.constraints = [distanceConstraint]
This assumes that the circle from which the player cannot move is an SKNode called limitcircle and it's anchor point is set to (0.5, 0.5) i,e, it's center. The above code would constrain the player to a distance of between 0 and 1/2 the width of the circle (i.e. it's radius) from a point at the center of the circle.