I was wondering how I would be able to choose a random SKColor for a node out of Red, Blue or Green color?
At the moment, I am just setting the Node color by block.fillColor = SKColor.redColor()
Thanks :)
You can use create with red/green/blue:
SKColor(red: Float(arc4random_uniform(255))/255.0,
green: Float(arc4random_uniform(255))/255.0 ,
blue: Float(arc4random_uniform(255))/255.0 ,
alpha: 1.0)
This will generate a random color. You can use the same principle in the HSV space....
Another version if you create a randomTo1() func which return a CGFloat random number from 0 to 1:
func randomTo1() -> CGFloat{
return CGFloat(arc4random_uniform(255))/255.0
}
let color=SKColor(red: randomTo1(),green: randomTo1(), blue: randomTo1(), alpha:1.0)
Here is a solution for RGB and HSB:
func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
func trulyRandomColor() -> SKColor {
return SKColor(red: random(), green: random(), blue: random(), alpha: 1.0)
}
func niceRandomColor() -> SKColor {
return SKColor(hue: random(), saturation: 1.0, brightness: 1.0, alpha: 1.0)
}
EDIT: Something like this works if you only need certain colors and want to choose randomly between them:
import GameKit
struct ColorGenerator {
private let distribution : GKRandomDistribution
private let possibleColors = [
SKColor.redColor(),
SKColor.blueColor(),
SKColor.greenColor(),
SKColor.purpleColor(),
SKColor.yellowColor(),
SKColor.brownColor(),
SKColor.whiteColor()
]
init() {
distribution = GKShuffledDistribution(lowestValue: 0, highestValue: possibleColors.count - 1)
}
func random() -> SKColor {
return possibleColors[distribution.nextInt()]
}
}
let generator = ColorGenerator()
for _ in 1...100 {
generator.random()
}
I used GKShuffledDistribution here to avoid colors being repeated and to make it look more "random" (even though it's not). You could also replace it with GKRandomDistribution, but then you'd maybe have 5 times the same colour in a row which isn't very desirable usually.
Related
I have a swiftUI animation based on some state:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
Is there any callback which is triggered when the above animation completes?
If there are any suggestions on how to accomplish an animation with a completion block in SwiftUI which are not withAnimation, I'm open to those as well.
I would like to know when the animation completes so I can do something else, for the purpose of this example, I just want to print to console when the animation completes.
Unfortunately there's no good solution to this problem (yet).
However, if you can specify the duration of an Animation, you can use DispatchQueue.main.asyncAfter to trigger an action exactly when the animation finishes:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
print("Animation finished")
}
Here's a bit simplified and generalized version that could be used for any single value animations. This is based on some other examples I was able to find on the internet while waiting for Apple to provide a more convenient way:
struct AnimatableModifierDouble: AnimatableModifier {
var targetValue: Double
// SwiftUI gradually varies it from old value to the new value
var animatableData: Double {
didSet {
checkIfFinished()
}
}
var completion: () -> ()
// Re-created every time the control argument changes
init(bindedValue: Double, completion: #escaping () -> ()) {
self.completion = completion
// Set animatableData to the new value. But SwiftUI again directly
// and gradually varies the value while the body
// is being called to animate. Following line serves the purpose of
// associating the extenal argument with the animatableData.
self.animatableData = bindedValue
targetValue = bindedValue
}
func checkIfFinished() -> () {
//print("Current value: \(animatableData)")
if (animatableData == targetValue) {
//if animatableData.isEqual(to: targetValue) {
DispatchQueue.main.async {
self.completion()
}
}
}
// Called after each gradual change in animatableData to allow the
// modifier to animate
func body(content: Content) -> some View {
// content is the view on which .modifier is applied
content
// We don't want the system also to
// implicitly animate default system animatons it each time we set it. It will also cancel
// out other implicit animations now present on the content.
.animation(nil)
}
}
And here's an example on how to use it with text opacity animation:
import SwiftUI
struct ContentView: View {
// Need to create state property
#State var textOpacity: Double = 0.0
var body: some View {
VStack {
Text("Hello world!")
.font(.largeTitle)
// Pass generic animatable modifier for animating double values
.modifier(AnimatableModifierDouble(bindedValue: textOpacity) {
// Finished, hurray!
print("finished")
// Reset opacity so that you could tap the button and animate again
self.textOpacity = 0.0
}).opacity(textOpacity) // bind text opacity to your state property
Button(action: {
withAnimation(.easeInOut(duration: 1.0)) {
self.textOpacity = 1.0 // Change your state property and trigger animation to start
}
}) {
Text("Animate")
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
On this blog this Guy Javier describes how to use GeometryEffect in order to have animation feedback, in his example he detects when the animation is at 50% so he can flip the view and make it looks like the view has 2 sides
here is the link to the full article with a lot of explanations: https://swiftui-lab.com/swiftui-animations-part2/
I will copy the relevant snippets here so the answer can still be relevant even if the link is not valid no more:
In this example #Binding var flipped: Bool becomes true when the angle is between 90 and 270 and then false.
struct FlipEffect: GeometryEffect {
var animatableData: Double {
get { angle }
set { angle = newValue }
}
#Binding var flipped: Bool
var angle: Double
let axis: (x: CGFloat, y: CGFloat)
func effectValue(size: CGSize) -> ProjectionTransform {
// We schedule the change to be done after the view has finished drawing,
// otherwise, we would receive a runtime error, indicating we are changing
// the state while the view is being drawn.
DispatchQueue.main.async {
self.flipped = self.angle >= 90 && self.angle < 270
}
let a = CGFloat(Angle(degrees: angle).radians)
var transform3d = CATransform3DIdentity;
transform3d.m34 = -1/max(size.width, size.height)
transform3d = CATransform3DRotate(transform3d, a, axis.x, axis.y, 0)
transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)
let affineTransform = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))
return ProjectionTransform(transform3d).concatenating(affineTransform)
}
}
You should be able to change the animation to whatever you want to achieve and then get the binding to change the state of the parent once it is done.
You need to use a custom modifier.
I have done an example to animate the offset in the X-axis with a completion block.
struct OffsetXEffectModifier: AnimatableModifier {
var initialOffsetX: CGFloat
var offsetX: CGFloat
var onCompletion: (() -> Void)?
init(offsetX: CGFloat, onCompletion: (() -> Void)? = nil) {
self.initialOffsetX = offsetX
self.offsetX = offsetX
self.onCompletion = onCompletion
}
var animatableData: CGFloat {
get { offsetX }
set {
offsetX = newValue
checkIfFinished()
}
}
func checkIfFinished() -> () {
if let onCompletion = onCompletion, offsetX == initialOffsetX {
DispatchQueue.main.async {
onCompletion()
}
}
}
func body(content: Content) -> some View {
content.offset(x: offsetX)
}
}
struct OffsetXEffectModifier_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Text("Hello")
.modifier(
OffsetXEffectModifier(offsetX: 10, onCompletion: {
print("Completed")
})
)
}
.frame(width: 100, height: 100, alignment: .bottomLeading)
.previewLayout(.sizeThatFits)
}
}
You can try VDAnimation library
Animate(animationStore) {
self.someState =~ newState
}
.duration(0.1)
.curve(.linear)
.start {
...
}
I have this code:
// Border Color
#State private var activeWindowBorderColorText: String = UserDefaults.standard.string(forKey: "ActiveWindowBorderColor") ?? "775759"
let red = $activeWindowBorderColorText[1..<2]
#State private var activeWindowBorderColor = Color(.sRGB, red: red, green: 1, blue: 1)
What must I do to get the red, green and blue component from the string as parameters for the Color()?
So, how to get the substrings?
I tried the extension from here:
How does String substring work in Swift
but it gives me:
Cannot use instance member '$activeWindowBorderColorText' within property initializer; property initializers run before 'self' is available
How about computed properties?
var red: String { activeWindowBorderColorText[1..<2] }
They update with the state.
Thank you both for the reply, I got it to work with a combination of .onAppear() and computed properties.
The conversion looks ugly, but it works :-)
.onAppear() {
print("initial color: \(activeWindowBorderColorText)")
var redHex: String { activeWindowBorderColorText[4..<6] }
let redDec = Int(redHex, radix:16)
let red = Double(redDec ?? 1)
var greenHex: String { activeWindowBorderColorText[6..<8] }
let greenDec = Int(greenHex, radix:16)
let green = Double(greenDec ?? 1)
var blueHex: String { activeWindowBorderColorText[8..<10] }
let blueDec = Int(blueHex, radix:16)
let blue = Double(blueDec ?? 1)
print("color: \(activeWindowBorderColorText) redHex: \(redHex ) redDec: \(redDec ?? 1) red:\(red)")
print("color: \(activeWindowBorderColorText) greenHex: \(redHex ) greenDec: \(redDec ?? 1) green:\(green)")
print("color: \(activeWindowBorderColorText) blueHex: \(redHex ) blueDec: \(redDec ?? 1) blue:\(blue)")
activeWindowBorderColor = Color(.sRGB, red: red, green: green, blue: blue)
}
I'm new to SpriteKit and I'm trying to simulate a population of 1000 moving individuals each being represented by a simple circle. I have an awful frame rate (around 6/7 fps) so I reduced to 100 and reached 35 fps...
My nodes are the simplest in the world, I'm using the entity component system from GameplayKit, but nothing fancy.
So what can I do to improve and reach 60 fps?
First my shape entity, later in the process the color and filling will change for some individuals, but here there's only a simple circle node:
class ShapeComponent : GKComponent {
let node = SKShapeNode(ellipseOf: CGSize(width: 10, height: 10))
override func didAddToEntity() {
node.entity = entity
}
override func willRemoveFromEntity() {
node.entity = nil
}
}
Then my movement component, having just a speed and an angle. I have even stored sine and cosine to limit computation:
class MovementComponent : GKComponent {
var speed : CGFloat = 25
var direction : CGFloat = .random(in: 0...(2 * .pi)) {
didSet { updateSinCos() }
}
var sinDir : CGFloat = 0
var cosDir : CGFloat = 0
override init() {
super.init()
updateSinCos()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shapeComponent: ShapeComponent {
guard let shapeComponent = entity?.component(ofType: ShapeComponent.self)
else { fatalError("A MovementComponent's entity must have a ShapeComponent") }
return shapeComponent
}
func updateSinCos() {
sinDir = sin(direction)
cosDir = cos(direction)
}
override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
// Declare local versions of computed properties so we don't compute them multiple times.
let delta = speed * CGFloat(deltaTime)
let vector = CGVector(dx: delta * sinDir, dy: delta * cosDir)
let node = shapeComponent.node
let currentPosition = node.position
node.position = CGPoint(x: currentPosition.x + vector.dx, y: currentPosition.y + vector.dy)
}
}
And at last my entity, with movement and shape components:
class IndividualEntity : GKEntity {
var shapeComponent: ShapeComponent {
guard let shapeComponent = component(ofType: ShapeComponent.self)
else { fatalError("A IndividualEntity must have a ShapeComponent") }
return shapeComponent
}
var movementComponent: MovementComponent {
guard let movementComponent = component(ofType: MovementComponent.self)
else { fatalError("A IndividualEntity must have a MovementComponent") }
return movementComponent
}
override init() {
super.init()
addComponent(ShapeComponent())
addComponent(MovementComponent())
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In my scene:
func setUpScene() {
for _ in 1...100 {
let position = CGPoint(x: .random(in: 0..<size.width) - 0.5*size.width, y: .random(in: 0..<size.height) - 0.5*size.height)
addIndividual(at: position)
}
}
var componentSystem = GKComponentSystem(componentClass: MovementComponent.self)
var individuals = [IndividualEntity]()
func addIndividual(at pos: CGPoint) {
let individual = IndividualEntity()
individual.shapeComponent.node.position = pos
addChild(individual.shapeComponent.node)
componentSystem.addComponent(foundIn: individual)
individuals.append(individual)
}
var lastUpdateTimeInterval: TimeInterval = 0
let maximumUpdateDeltaTime: TimeInterval = 1.0 / 60.0
override func update(_ currentTime: TimeInterval) {
guard view != nil else { return }
var deltaTime = currentTime - lastUpdateTimeInterval
deltaTime = deltaTime > maximumUpdateDeltaTime ? maximumUpdateDeltaTime : deltaTime
lastUpdateTimeInterval = currentTime
componentSystem.update(deltaTime: deltaTime)
}
So if someone can help me with this problem... I just want those circles to wander, and later respond to collision by changing direction and color.
Thanks in advance.
I've updated my Xcode to the latest version, currently it's 10.2.1(10E1001) and migrated my project from Swift 4 to Swift 5.
It made me some troubles, but finally I've built my project and it works correctly from debug version on my iPhone.
After that I've had few troubles with archiving my project (maybe it could be a reason)
I've upload it in App Store and after that tried my app at TestFlight.
Plus, for some reason few code in my project works wrong.
It seems like collectionView(didSelectItemAtIndexPath...) doesn't work (but it perfectly works in Xcode) and my custom layout of collectionView doesn't work too (but also works on Debug).
It seems like layout works wrong, but I can't understand what's the difference between Debug and Release version except provisioning profile.
I can share you more videos, code, w/e you need, I really need to resolve this issue.
I've not found anything else like that in the web
I've taken that custom layout code from here https://codereview.stackexchange.com/questions/197017/page-and-center-uicollectionview-like-app-store
class SnapPagingLayout: UICollectionViewFlowLayout {
private var centerPosition = true
private var peekWidth: CGFloat = 0
private var indexOfCellBeforeDragging = 0
convenience init(centerPosition: Bool = true, peekWidth: CGFloat = 40, spacing: CGFloat? = nil, inset: CGFloat? = nil) {
self.init()
self.scrollDirection = .horizontal
self.centerPosition = centerPosition
self.peekWidth = peekWidth
if let spacing = spacing {
self.minimumLineSpacing = spacing
}
if let inset = inset {
self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
}
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
self.itemSize = calculateItemSize(from: collectionView.bounds.size)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView,
!newBounds.size.equalTo(collectionView.bounds.size) else {
return false
}
itemSize = calculateItemSize(from: collectionView.bounds.size)
return true
}
}
private extension SnapPagingLayout {
func calculateItemSize(from bounds: CGSize) -> CGSize {
return CGSize(
width: bounds.width - peekWidth * 2,
height: (bounds.width - peekWidth * 2) / 1.77
)
}
func indexOfMajorCell() -> Int {
guard let collectionView = collectionView else { return 0 }
let proportionalOffset = collectionView.contentOffset.x
/ (itemSize.width + minimumLineSpacing)
return Int(round(proportionalOffset))
}
}
extension SnapPagingLayout {
func willBeginDragging() {
indexOfCellBeforeDragging = indexOfMajorCell()
}
func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let collectionView = collectionView else { return }
// Stop scrollView sliding
targetContentOffset.pointee = collectionView.contentOffset
// Calculate where scrollView should snap to
let indexOfMajorCell = self.indexOfMajorCell()
guard let dataSourceCount = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: 0),
dataSourceCount > 0 else {
return
}
// Calculate conditions
let swipeVelocityThreshold: CGFloat = 0.3 // After some trail and error
let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < dataSourceCount && velocity.x > swipeVelocityThreshold
let hasEnoughVelocityToSlideToThePreviousCell = indexOfCellBeforeDragging - 1 >= 0 && velocity.x < -swipeVelocityThreshold
let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging
let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging
&& (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell)
guard didUseSwipeToSkipCell else {
// Better way to scroll to a cell
collectionView.scrollToItem(
at: IndexPath(row: indexOfMajorCell, section: 0),
at: centerPosition ? .centeredHorizontally : .left, // TODO: Left ignores inset
animated: true
)
return
}
let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1)
var toValue = CGFloat(snapToIndex) * (itemSize.width + minimumLineSpacing)
if centerPosition {
// Back up a bit to center
toValue = toValue - peekWidth + sectionInset.left
}
// Damping equal 1 => no oscillations => decay animation
UIView.animate(
withDuration: 0.3,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: velocity.x,
options: .allowUserInteraction,
animations: {
collectionView.contentOffset = CGPoint(x: toValue, y: 0)
collectionView.layoutIfNeeded()
},
completion: nil
)
}
}
I wanna see page and center collection view like in App Store. And also I wanna make my didSelect-method work correctly.
This is a bug for Swift 5.0 compiler related to this references:
https://bugs.swift.org/browse/SR-10257
.
Update:
Further searching found an temporary answer at this link on Stackoverflow
You can work around it by explicitly tagging it with #objc for now.
I'm writing an app building elements consisting of CGPoints. I have 2 buttons: makeRectangle and makeTriangle. For building/drawing stage I use three methods for rectangle and three methods for triangle inside drawRect.
I'm stuck with my code in drawRect. In if-else-statement each method swaps the building/drawing scheme for previous element every time a button pressed.
If I already have built rectangle and then I click makeTriangle button, I get new triangle but my rectangle turns into triangle with one unconnected point.
Is there a workaround or I shouldn't use drawRect method?
Here's an SO post on the drawRect topic: To drawRect or not to drawRect
Element declaration:
enum Element {
case point1(point: CGPoint)
case point2(point: CGPoint)
case point3(point: CGPoint)
case point4(point: CGPoint)
func coord() -> [CGPoint] {
switch self {
case .point1(let point): return [point]
case .point2(let point): return [point]
case .point3(let point): return [point]
case .point4(let point): return [point]
}
}
func buildQuadPath(path: CGMutablePath) {
switch self {
case .point1(let point): CGPathMoveToPoint(path, nil, point.x, point.y)
case .point2(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point3(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point4(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
CGPathCloseSubpath(path)
}
}
func buildTriPath(path: CGMutablePath) {
switch self {
case .point1(let point): CGPathMoveToPoint(path, nil, point.x, point.y)
case .point2(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point3(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
default:
CGPathCloseSubpath(path)
}
}
}
Methods for building and drawing triangle and rectangle:
func buildTriPath() -> CGMutablePath {
let path = CGPathCreateMutable()
_ = array.map { $0.buildTriPath(path) }
return path
}
func drawTriPath() {
let path = buildTriPath()
GraphicsState {
CGContextAddPath(self.currentContext, path)
CGContextStrokePath(self.currentContext)
}
}
func drawTriFill() {
let fill = buildTriPath()
GraphicsState {
CGContextAddPath(self.currentContext, fill)
CGContextFillPath(self.currentContext)
}
}
///////////////////////////////////////////////////////
func buildQuadPath() -> CGMutablePath {
let path = CGPathCreateMutable()
_ = array.map { $0.buildQuadPath(path) }
return path
}
func drawQuadPath() {
let path = buildQuadPath()
GraphicsState {
CGContextAddPath(self.currentContext, path)
CGContextStrokePath(self.currentContext)
}
}
func drawQuadFill() {
let fill = buildQuadPath()
GraphicsState {
CGContextAddPath(self.currentContext, fill)
CGContextFillPath(self.currentContext)
}
}
Two variables help determine whether button is pressed:
var squareB: Int = 0
var triangleB: Int = 0
#IBAction func makeTriangle(sender: AnyObject?) {
....................
....................
triangleB += 1
squareB = 0
}
#IBAction func makeRectangle(sender: AnyObject?) {
....................
....................
triangleB = 0
squareB += 1
}
drawRect method:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
drawBG()
GraphicsState { self.drawMyPoints() }
if squareB >= 1 && triangleB == 0 {
buildQuadPath()
drawQuadPath()
drawQuadFill()
needsDisplay = true
}
else if triangleB >= 1 && squareB == 0 {
buildTriPath()
drawTriPath()
drawTriFill()
needsDisplay = true
}
drawBorder()
}
...and at last a Context.swift file:
import Cocoa
import CoreGraphics
extension NSView {
var currentContext : CGContext? {
get {
let unsafeContextPointer = NSGraphicsContext.currentContext()?.graphicsPort
if let contextPointer = unsafeContextPointer {
let opaquePointer = COpaquePointer(contextPointer)
let context: CGContextRef = Unmanaged.fromOpaque(opaquePointer).takeUnretainedValue()
return context }
else { return nil }
}
}
func GraphicsState(drawStuff: () -> Void) {
CGContextSaveGState(currentContext)
drawStuff()
CGContextRestoreGState(currentContext)
}
}
//the end of code
Okay, since I could use the practice I created an example project to show what vikingosegundo and I mean.
Here's the gist of it:
For this example I kept all relevant code except the adding and removing of shapes in a GHShapeDemoView.
I used structs to define the shapes, but treat them as one "unit" of data that is handled during drawing, adding to the view, etc. All shapes are kept in an array and during drawing that is iterated and all found shapes are drawn using a simple NSBezierPath.
For simplicity's sake I just chose some random fixed points for each shape, in a real project that would obviously be determined in another way (I was too lazy to add input fields...).
Even here there are a lot of ways to refactor (probably). For example, one could even make each shape a class of its own (or use one class for shapes in general). Maybe even a subclass of NSView, that would then result in the "drawing area" itself not be a custom view, but a normal one and on button presses relevant shape views would be added as subviews. That would then probably also get rid of all this points-calculating stuff (mostly).
In a real project I would probably have gone for shapes as layer subclasses that I then add to the subview. I'm no expert, but I think that might have performance benefits depending on how many shapes there are and whether or not I would animate them.
(Obviously the highest performance would probably be gained from using OpenGL ES or something, but I have no clue about that and that's far beyond the scope of this question).
I hope this provides you with a good starting point to work on your drawing. As stated above I would strongly suggest restructuring your project in a similar way to properly define a flow of what you draw and how you draw it. If you somehow must rely on keeping points data in enums or structs or something, write adequate mappers to your drawing data structure.
if (makeTriangle != nil) { and if (makeRectangle != nil) { doesnt make much sense. according to your comment, makerRectangle and makeTriangle are buttons. By your statements you are checking their existence — and we can assume they always exists — the first if-clause will always be executed.
what you want instead: create method that will be executed by the buttons. Each of this method will set either a combination of bool values or a single enum value and then tell the view to redraw by calling setNeedsDisplay().
Here is a code written by Gero:
This code works well with Swift 2.2.
//
// GHShapeDemoView.swift
// GHShapeDrawingExample
//
// Created by Gero Herkenrath on 21/10/2016.
// Copyright © 2016 Gero Herkenrath. All rights reserved.
//
import Cocoa
class GHShapeDemoView: NSView {
struct Shape {
var p1:CGPoint = NSMakePoint(0.0, 0.0)
var p2:CGPoint = NSMakePoint(0.0, 0.0)
var p3:CGPoint = NSMakePoint(0.0, 0.0)
var p4:CGPoint?
}
var shapes:[Shape] = []
override internal var flipped: Bool {
return true
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.whiteColor().setFill()
let updatedRect = NSBezierPath.init(rect: dirtyRect)
updatedRect.fill()
for shape in shapes {
drawShape(shape)
}
}
func drawShape(shape:Shape) {
let shapePath = NSBezierPath()
shapePath.moveToPoint(shape.p1)
shapePath.lineToPoint(shape.p2)
shapePath.lineToPoint(shape.p3)
if let lastPoint = shape.p4 {
shapePath.lineToPoint(lastPoint)
}
shapePath.closePath()
NSColor.blackColor().setStroke()
shapePath.stroke()
}
func addTrapezoid(p1:NSPoint, p2:NSPoint, p3:NSPoint, p4:NSPoint) {
var shape = Shape()
shape.p1 = p1
shape.p2 = p2
shape.p3 = p3
shape.p4 = p4
shapes.append(shape)
}
func addTriangle(p1:NSPoint, p2:NSPoint, p3:NSPoint) {
var shape = Shape()
shape.p1 = p1
shape.p2 = p2
shape.p3 = p3
shapes.append(shape)
}
func removeShapeAt(index:Int) {
if index < shapes.count {
shapes.removeAtIndex(index)
}
}
}
/////////////////////////////////////////////////
//
// ViewController.swift
// GHShapeDrawingExample
//
// Created by Gero Herkenrath on 21/10/2016.
// Copyright © 2016 Gero Herkenrath. All rights reserved.
//
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
if #available(OSX 10.10, *) {
super.viewDidLoad()
}
else {
// Fallback on earlier versions
}
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
// first Shape
let pointA1 = NSMakePoint(115.0, 10.0)
let pointA2 = NSMakePoint(140.0, 10.0)
let pointA3 = NSMakePoint(150.0, 40.0)
let pointA4 = NSMakePoint(110.0, 40.0)
// second Shape
let pointB1 = NSMakePoint(230.0, 10.0)
let pointB2 = NSMakePoint(260.0, 40.0)
let pointB3 = NSMakePoint(200.0, 40.0)
// thirdShape
let pointC1 = NSMakePoint(115.0, 110.0)
let pointC2 = NSMakePoint(140.0, 110.0)
let pointC3 = NSMakePoint(150.0, 140.0)
let pointC4 = NSMakePoint(110.0, 140.0)
#IBOutlet weak var shapeHolderView: GHShapeDemoView!
#IBAction func newTrapezoid(sender: AnyObject) {
if shapeHolderView.shapes.count < 1 {
shapeHolderView.addTrapezoid(pointA1, p2: pointA2, p3: pointA3, p4: pointA4)
}
else {
shapeHolderView.addTrapezoid(pointC1, p2: pointC2, p3: pointC3, p4: pointC4)
}
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
#IBAction func newTriangle(sender: AnyObject) {
shapeHolderView.addTriangle(pointB1, p2: pointB2, p3: pointB3)
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
#IBAction func removeLastShape(sender: AnyObject) {
if shapeHolderView.shapes.count > 0 {
shapeHolderView.removeShapeAt(shapeHolderView.shapes.count - 1)
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
}
}