#IBDesignable doesn't work in "old" project - xcode

I have UIView subclass, for example, this:
#IBDesignable
class GradientView: UIView {
#IBInspectable var firstColor: UIColor = UIColor.red
#IBInspectable var secondColor: UIColor = UIColor.green
#IBInspectable var vertical: Bool = true
override func awakeFromNib() {
super.awakeFromNib()
applyGradient()
}
func applyGradient() {
let colors = [firstColor.cgColor, secondColor.cgColor]
let layer = CAGradientLayer()
layer.colors = colors
layer.frame = self.bounds
layer.startPoint = CGPoint(x: 0, y: 0)
layer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
self.layer.addSublayer(layer)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
applyGradient()
}
}
It successfully renders in Interface Builder for a new project, but it doesn't work for my "old" project.
Does anyone know why it happens?

Related

How draw a rectangle hole on UIBlurEffect and move it on x axis (UIKit)

I'm trying to create blur effect on a view and than add a shape which will show image on this blurred layer (custom video editing functionality)
Currently I'm able to do it only dragging mask view from the right edge:
but when I try to do it from the left edge, I get such a effect:
func configureBlurView() {
let viewHeight: CGFloat = 60
let padding: CGFloat = 10
blurView = UIView()
blurView.layer.cornerRadius = 10
blurView.clipsToBounds = true
blurView.isHidden = true
blurView.translatesAutoresizingMaskIntoConstraints = false
addSubview(blurView)
addConstraints([
blurView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding),
blurView.bottomAnchor.constraint(equalTo: stackView.topAnchor, constant: -padding),
blurView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding),
blurView.heightAnchor.constraint(equalToConstant: viewHeight)
])
addBlurEffect(for: blurView)
}
private func addBlurEffect(for view: UIView) {
let blurEffect = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blurEffect.alpha = 0.5
blurEffect.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blurEffect)
addConstraints([
blurEffect.topAnchor.constraint(equalTo: view.topAnchor),
blurEffect.leadingAnchor.constraint(equalTo: view.leadingAnchor),
blurEffect.bottomAnchor.constraint(equalTo: view.bottomAnchor),
blurEffect.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
private func makeClearHole(rect: CGRect) {
let maskLayer = CAShapeLayer()
maskLayer.fillColor = UIColor.black.cgColor
let pathToOverlay = CGMutablePath()
pathToOverlay.addRect(blurView.bounds)
pathToOverlay.addRect(rect)
maskLayer.path = pathToOverlay
maskLayer.fillRule = .evenOdd
maskLayer.cornerRadius = 10
blurView.layer.mask = maskLayer
}
I'm using touchesMoved method to change orange view dimensions:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard trimmerView.isHidden == false else { return }
if let touch = touches.first{
let currentTouchPoint = touch.location(in: self)
let previousTouchPoint = touch.previousLocation(in: self)
let deltaX = currentTouchPoint.x - previousTouchPoint.x
if trimmerView.bounds.width >= 70 {
if touchStartEdge.middle {
if trimmerViewLeadingConstraint.constant < 10 {
trimmerViewLeadingConstraint.constant = 10
} else if trimmerViewTrailingConstraint.constant > -10 {
trimmerViewTrailingConstraint.constant = -10
} else {
trimmerViewLeadingConstraint.constant += deltaX
trimmerViewTrailingConstraint.constant += deltaX
}
}
if touchStartEdge.leftEdge {
if trimmerViewLeadingConstraint.constant >= 10.0 {
trimmerViewLeadingConstraint.constant += deltaX
} else if trimmerViewLeadingConstraint.constant < 10.0 {
trimmerViewLeadingConstraint.constant = 10
}
}
if touchStartEdge.rightEdge {
if trimmerViewTrailingConstraint.constant <= -10 {
trimmerViewTrailingConstraint.constant += deltaX
} else if trimmerViewTrailingConstraint.constant > -10 {
trimmerViewTrailingConstraint.constant = -10.0
}
}
}
updateProgressBarConstraints()
makeClearHole(rect: CGRect(x: 0, y: 0, width: trimmerView.frame.width, height: trimmerView.frame.height))
UIView.animate(withDuration: 0.10, delay: 0, options: .curveEaseIn) { [weak self] in
self?.layoutIfNeeded()
}
}
}
What I'd like to achieve is to remove blur effect only in bounds of orange view.
Any ideas ?? :)
Thanks for help!!
Couple ways to do this - here's one...
Add a mask to the blur effect view. As the user drags the "trimmer" update the mask.
Here's a quick example...
We'll:
create a stack view with 10 images
overlay that with a masked blur effective view
add a "draggable trimmer view"
when we drag the trimmer, we update the mask
Example View Controller
class TrimmerVC: UIViewController {
var blurView: MaskedBlurView!
let trimmerView = DragView()
let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
// respect safe area when we setup constraints
let g = view.safeAreaLayoutGuide
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
stackView.heightAnchor.constraint(equalToConstant: 80.0),
])
// let's add 10 imageviews to the stack view
for i in 1...10 {
if let img = UIImage(systemName: "\(i).circle.fill") {
let imgView = UIImageView(image: img)
imgView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
stackView.addArrangedSubview(imgView)
}
}
let blurEffect = UIBlurEffect(style: .dark)
blurView = MaskedBlurView(effect: blurEffect)
blurView.alpha = 0.5
blurView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blurView)
NSLayoutConstraint.activate([
blurView.topAnchor.constraint(equalTo: stackView.topAnchor, constant: 0.0),
blurView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 0.0),
blurView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0.0),
blurView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 0.0),
])
trimmerView.backgroundColor = .systemOrange
view.addSubview(trimmerView)
trimmerView.didDrag = { [weak self] newX in
guard let self = self else { return }
self.blurView.clearX = newX - self.stackView.frame.origin.x
}
}
// we'll use this to update the framing when the stack view width changes
// such as on device rotation
var curStackW: CGFloat = -1
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if curStackW != stackView.frame.width {
curStackW = stackView.frame.width
var r = stackView.frame
r.origin.y += r.size.height + 20.0
r.size.width = 160
r.size.height = 40
trimmerView.frame = r
blurView.clearWidth = trimmerView.frame.width
blurView.clearX = 0
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// toggle the trimmer view between
// below the stack view and
// overlaid on the stack view
if trimmerView.frame.origin.y > stackView.frame.origin.y {
let r = stackView.frame
trimmerView.frame.origin.y = r.origin.y - 6.0
trimmerView.frame.size.height = r.height + 12.0
} else {
let r = stackView.frame
trimmerView.frame.origin.y = r.origin.y + r.height + 12.0
trimmerView.frame.size.height = 60.0
}
}
}
Example Draggable "Trimmer" View
class DragView: UIView {
var didDrag: ((CGFloat) -> ())?
let maskLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
maskLayer.fillColor = UIColor.red.cgColor
layer.mask = maskLayer
}
override func layoutSubviews() {
super.layoutSubviews()
let pathToOverlay = CGMutablePath()
pathToOverlay.addRect(bounds)
pathToOverlay.addRect(bounds.insetBy(dx: 20.0, dy: 8.0))
maskLayer.path = pathToOverlay
maskLayer.fillRule = .evenOdd
maskLayer.cornerRadius = 10
}
var touchStartX: CGFloat = 0
var frameStartX: CGFloat = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
touchStartX = touch.location(in: self.superview!).x
frameStartX = self.frame.origin.x
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let loc = touch.location(in: self.superview!)
self.frame.origin.x = frameStartX + (loc.x - touchStartX)
didDrag?(self.frame.origin.x)
}
}
Example Masked Blur View
class MaskedBlurView: UIVisualEffectView {
public var clearWidth: CGFloat = 100 {
didSet { updateMask() }
}
public var clearX: CGFloat = 0 {
didSet { updateMask() }
}
private let maskLayer = CAShapeLayer()
override init(effect: UIVisualEffect?) {
super.init(effect: effect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
maskLayer.fillColor = UIColor.red.cgColor
layer.mask = maskLayer
}
func updateMask() {
let leftR = CGRect(x: 0, y: 0, width: clearX, height: bounds.height)
let rightR = CGRect(x: clearX + clearWidth, y: 0, width: bounds.width, height: bounds.height)
let bez = UIBezierPath(rect: leftR)
bez.append(UIBezierPath(rect: rightR))
maskLayer.path = bez.cgPath
}
override func layoutSubviews() {
super.layoutSubviews()
maskLayer.frame = bounds
}
}
When running (in landscape orientation) it will start like this:
I placed the "trimmer" view below the stack view to make it a little more clear what's happening.
As we drag the trimmer view, the blur view's mask will be updated:
Tapping anywhere in an empty part of the screen will toggle the trimmer view between "under the stack view" and "overlaid on the stack view":
This was just put together quickly -- you should have no trouble restructuring the code to wrap everything into a single custom view (or however it would work best for your needs).

SwiftUI exporting the content of Canvas

Does anyone know how to export the content of a Canvas into an Image?
With SwiftUI, it is possible to generate an Image from a View with an extension
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
This works great for simple views like Button, but for Canvas it always generates an empty image.
For example, with the following code, the image generated by the button is fine, but the one of the Canvas is always empty.
import SwiftUI
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct ContentView: View {
var textView: some View {
Text("Hello, SwiftUI")
.padding()
.background(Color.green)
.foregroundColor(.white)
.clipShape(Capsule())
}
var canvas: some View {
Canvas { context, size in
var path = Path()
path.move(to: CGPoint(x: 0, y:0))
path.addLine(to: CGPoint(x: size.width/2, y:0))
path.addLine(to: CGPoint(x: size.width/2, y:size.height/2))
path.addLine(to: CGPoint(x: 0, y:size.height/2))
path.closeSubpath()
context.fill(path, with: .color(.blue))
}
}
var body: some View {
VStack {
textView
canvas
Button("Save to image: Canvas") {
if let view = canvas as? Canvas<EmptyView> {
let image = view.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
Button("Save to image: Text") {
if let view = textView as? Text {
let image = view.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
}
}
Apply a frame to the canvas and it should work. E.g.
canvas.frame(width: 300, height: 300)
Answer is here: Swift UI exporting content of canvas. Apply the frame in the line where you get the snapshot, i.e.
let newImage = canvasView.frame(width: 300, height: 300).snapshot()

Continuously Redrawing a Path with Updated Data

I am developing an audio visualizer MacOS app, and I want to use Quartz/CoreGraphics to render the time-varying spectrum coordinated with the playing audio. My Renderer code is:
import Cocoa
class Renderer: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.white.setFill()
bounds.fill()
guard let context = NSGraphicsContext.current?.cgContext else {return}
var x : CGFloat = 0.0
var y : CGFloat = 0.0
context.beginPath()
context.move(to: CGPoint(x: x, y: y))
for bin in 0 ..< 300 {
x = CGFloat(bin)
y = CGFloat(Global.spectrum[bin])
context.addLine(to: CGPoint(x: x, y: y))
}
context.setStrokeColor(CGColor( red: 1, green: 0, blue: 0, alpha: 1))
context.setLineWidth(1.0)
context.strokePath()
self.setNeedsDisplay(dirtyRect)
}
}
This draws the path once - using the initial all-zeroes values of the spectrum[] array - and then continues to draw that same all-zeroes line indefinitely. It does not update using the new values in the spectrum[] array. I used a print() statement to verify that the values themselves are being updated, but the draw function does not redraw the path using the updated spectrum values. What am I doing wrong?
The following demo shows how to update an NSView with random numbers created by a timer in a separate class to hopefully mimic your project. It may be run in Xcode by setting up a Swift project for MacOS, copy/pasting the source code into a new file called 'main.swift', and deleting the AppDelegate supplied by Apple. A draw function similar to what you posted is used.
import Cocoa
var view : NSView!
var data = [Int]()
public extension Array where Element == Int {
static func generateRandom(size: Int) -> [Int] {
guard size > 0 else {
return [Int]()
}
return Array(0..<size).shuffled()
}
}
class DataManager: NSObject {
var timer:Timer!
#objc func fireTimer() {
data = Array.generateRandom(size:500)
view.needsDisplay = true
}
func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
}
func stopTimer() {
timer?.invalidate()
}
}
let dataMgr = DataManager()
class View: NSView {
override func draw(_ rect: NSRect) {
super.draw(rect)
NSColor.white.setFill()
bounds.fill()
guard let gc = NSGraphicsContext.current?.cgContext else {return}
var xOld : CGFloat = 0.0
var yOld : CGFloat = 0.0
var xNew : CGFloat = 0.0
var yNew : CGFloat = 0.0
var counter : Int = 0
gc.beginPath()
gc.move(to: CGPoint(x: xOld, y: yOld))
for i in 0 ..< data.count {
xNew = CGFloat(counter)
yNew = CGFloat(data[i])
gc.addLine(to: CGPoint(x: xNew, y: yNew))
xOld = xNew;
yOld = yNew;
counter = counter + 1
}
gc.setStrokeColor(CGColor( red: 1, green: 0, blue: 0, alpha: 1))
gc.setLineWidth(1.0)
gc.strokePath()
}
}
class ApplicationDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
#objc func myStartAction(_ sender:AnyObject ) {
dataMgr.startTimer()
}
#objc func myStopAction(_ sender:AnyObject ) {
dataMgr.stopTimer()
}
func buildMenu() {
let mainMenu = NSMenu()
NSApp.mainMenu = mainMenu
// **** App menu **** //
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
}
func buildWnd() {
data = Array.generateRandom(size: 500)
let _wndW : CGFloat = 800
let _wndH : CGFloat = 600
window = NSWindow(contentRect: NSMakeRect( 0, 0, _wndW, _wndH ), styleMask:[.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false)
window.center()
window.title = "Swift Test Window"
window.makeKeyAndOrderFront(window)
// **** Start Button **** //
let startBtn = NSButton (frame:NSMakeRect( 30, 20, 95, 30 ))
startBtn.bezelStyle = .rounded
startBtn.title = "Start"
startBtn.action = #selector(self.myStartAction(_:))
window.contentView!.addSubview (startBtn)
// **** Stop Button **** //
let stopBtn = NSButton (frame:NSMakeRect( 230, 20, 95, 30 ))
stopBtn.bezelStyle = .rounded
stopBtn.title = "Stop"
stopBtn.action = #selector(self.myStopAction(_:))
window.contentView!.addSubview (stopBtn)
// **** Custom view **** //
view = View( frame:NSMakeRect(20, 60, _wndW - 40, _wndH - 80))
view.autoresizingMask = [.width, .height]
window.contentView!.addSubview (view)
// **** Quit btn **** //
let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
quitBtn.bezelStyle = .circular
quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
quitBtn.title = "Q"
quitBtn.action = #selector(NSApplication.terminate)
window.contentView!.addSubview(quitBtn)
}
func applicationDidFinishLaunching(_ notification: Notification) {
buildMenu()
buildWnd()
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
let applicationDelegate = ApplicationDelegate()
// **** main.swift **** //
let application = NSApplication.shared
application.setActivationPolicy(NSApplication.ActivationPolicy.regular)
application.delegate = applicationDelegate
application.activate(ignoringOtherApps:true)
application.run()

How to make the frame of a button custom shape in Swift 2

I want the button to be reactive to tap only in the custom polygonal shape that I create and not in the CGRect frame.
button.frame only supports CGRect.
Here is an example of a button that only responds to touches within a certain area.
class MyButton: UIButton {
var path: UIBezierPath!
override func awakeFromNib() {
backgroundColor = UIColor.greenColor()
addTarget(self, action: #selector(touchDown), forControlEvents: .TouchDown)
}
override func drawRect(rect: CGRect) {
path = UIBezierPath()
path.moveToPoint(CGPointMake(150, 10))
path.addLineToPoint(CGPointMake(200, 10))
path.addLineToPoint(CGPointMake(150, 100))
path.addLineToPoint(CGPointMake(100, 100))
path.closePath()
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.fillColor = UIColor.blueColor().CGColor
shapeLayer.path = path.CGPath
layer.addSublayer(shapeLayer)
}
func touchDown(button: MyButton, event: UIEvent) {
if let touch = event.touchesForView(button)?.first {
let location = touch.locationInView(button)
if path.containsPoint(location) == false {
button.cancelTrackingWithEvent(nil)
}
}
}
}
If you want to do it in Swift 3/4:
class MyButton: UIButton {
var path: UIBezierPath!
override func awakeFromNib() {
backgroundColor = UIColor.green
addTarget(self, action: #selector(touchDown), for: .touchDown)
}
override func draw(_ rect: CGRect) {
path = UIBezierPath()
path.move(to: CGPoint(x: 150, y: 10))
path.addLine(to: CGPoint(x: 200, y: 10))
path.addLine(to: CGPoint(x: 150, y: 100))
path.addLine(to: CGPoint(x: 100, y: 100))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.path = path.cgPath
layer.addSublayer(shapeLayer)
}
func touchDown(button: MyButton, event: UIEvent) {
if let touch = event.touches(for: button)?.first {
let location = touch.location(in: button)
if path.contains(location) == false {
button.cancelTracking(with: nil)
}
}
}
}

didBeginContact works absolutely incorrect swift

I have a very simple app on sprite kit for mac os. (BTW, this code for iOS is working correctly).
AppDelegate code:
import Cocoa
import SpriteKit
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var skView: SKView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
/* Pick a size for the scene */
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
self.skView!.presentScene(scene)
/* Sprite Kit applies additional optimizations to improve rendering performance */
self.skView!.ignoresSiblingOrder = true
self.skView!.showsFPS = true
self.skView!.showsNodeCount = true
}
}
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
return true
}
}
And the scene code:
import Foundation
import SpriteKit
struct Detection {
static var no : UInt32 = 0
static var all : UInt32 = UInt32.max
static var monster : UInt32 = 0b1
static var suric : UInt32 = 0b10
static var ninja : UInt32 = 0b100
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
func / (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x / scalar, y: point.y / scalar)
}
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
return CGFloat(sqrtf(Float(a)))
}
#endif
extension CGPoint {
func length() -> CGFloat {
return sqrt(x*x + y*y)
}
func normalized() -> CGPoint {
return self / length()
}
}
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKSpriteNode(imageNamed: "player.png")
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
physicsWorld.gravity = CGVectorMake(0.0, 0.0)
physicsWorld.contactDelegate = self
player.position = CGPoint(x: self.size.width * 0.1, y: self.size.height / 2)
addChild(player)
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(createMonster), SKAction.waitForDuration(1)])))
}
override func mouseDown(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
let suric = SKSpriteNode(imageNamed: "projectile.png")
suric.position = player.position
suric.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
suric.physicsBody?.categoryBitMask = Detection.suric
suric.physicsBody?.collisionBitMask = Detection.no
suric.physicsBody?.contactTestBitMask = Detection.monster
suric.physicsBody?.usesPreciseCollisionDetection = true
suric.physicsBody?.dynamic = true
suric.physicsBody?.angularVelocity = -10.0
let offset = location - suric.position
if offset.x < 0 {
return
}
addChild(suric)
let direc = offset.normalized()
let shoot = direc * 1000
let dest = shoot + suric.position
let move = SKAction.moveTo(dest, duration: 2.0)
let stop = SKAction.removeFromParent()
suric.runAction(SKAction.sequence([move, stop]))
}
func suricHit(suric : SKSpriteNode?, monster : SKSpriteNode?) {
if suric != nil && monster != nil {
suric!.removeFromParent()
monster!.removeFromParent()
}
}
func didBeginContact(contact: SKPhysicsContact) {
var first : SKPhysicsBody
var second : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
first = contact.bodyA
second = contact.bodyB
}
else {
first = contact.bodyB
second = contact.bodyA
}
if (first.categoryBitMask & Detection.monster != 0) && (second.categoryBitMask & Detection.suric != 0) {
suricHit(first.node as? SKSpriteNode, monster: second.node as? SKSpriteNode)
}
}
func createMonster() {
let monster = SKSpriteNode(imageNamed: "monster.png")
let y = random(min: monster.size.height / 2, size.height - monster.size.height)
monster.position = CGPoint(x: self.size.width + monster.size.width / 2, y: y)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size, center: CGPoint(x: monster.position.x / 2, y: monster.position.y))
monster.physicsBody?.usesPreciseCollisionDetection = true
monster.physicsBody?.categoryBitMask = Detection.monster
monster.physicsBody?.contactTestBitMask = Detection.suric
monster.physicsBody?.collisionBitMask = Detection.no
monster.physicsBody?.dynamic = true
addChild(monster)
let duration = random(min: 2.0, 4.0)
let move = SKAction.moveTo(CGPoint(x: -monster.size.width / 2, y: y), duration: NSTimeInterval(duration))
let done = SKAction.removeFromParent()
monster.runAction(SKAction.sequence([move, done]))
}
}
And the contact function works really strange. It runs even without actual contact between suric and monster. I have no idea why this happens. Is it just my fault or just a Xcode bug?
Your physics body of projectile is too big. Nodes contacts through physics bodies not their actual sizes.
suric.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
change size of physics body to something smaller
self.size.width / 2
something like this
suric.physicsBody = SKPhysicsBody(circleOfRadius: suric.size);

Resources