Face detection swift vision kit - face-detection

I am trying Vision kit for iOS 11. I can use Vision and I can find boundbox values face. But I don't know how can I draw a rectangle using this points. I hope so my question is clear.

Hope you were able to use VNDetectFaceRectanglesRequest and able to detect faces. To show rectangle boxes there are lots of ways to achieve it. But simplest one would be using CAShapeLayer to draw layer on top your image for each face you detected.
Consider you have VNDetectFaceRectanglesRequest like below
let request = VNDetectFaceRectanglesRequest { [unowned self] request, error in
if let error = error {
// somthing is not working as expected
}
else {
// we got some face detected
self.handleFaces(with: request)
}
}
let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
do {
try handler.perform([request])
}
catch {
// catch exception if any
}
You can implement a simple method called handleFace for each face detected and use VNFaceObservation property to draw a CAShapeLayer.
func handleFaces(with request: VNRequest) {
imageView.layer.sublayers?.forEach { layer in
layer.removeFromSuperlayer()
}
guard let observations = request.results as? [VNFaceObservation] else {
return
}
observations.forEach { observation in
let boundingBox = observation.boundingBox
let size = CGSize(width: boundingBox.width * imageView.bounds.width,
height: boundingBox.height * imageView.bounds.height)
let origin = CGPoint(x: boundingBox.minX * imageView.bounds.width,
y: (1 - observation.boundingBox.minY) * imageView.bounds.height - size.height)
let layer = CAShapeLayer()
layer.frame = CGRect(origin: origin, size: size)
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 2
imageView.layer.addSublayer(layer)
}
}
More info can be found here in Github repo iOS-11-by-Examples

Here is easy and simple way to draw boxes.
let faceRequest = VNDetectFaceRectanglesRequest(completionHandler:self.faceDetection)
func faceDetection (request: VNRequest, error: Error?) {
guard let observations = request.results as? [VNFaceObservation]
else { print("unexpected result type from VNFaceObservation")
return }
guard observations.first != nil else {
return
}
// Show the pre-processed image
DispatchQueue.main.async {
self.resultImageView.subviews.forEach({ (subview) in
subview.removeFromSuperview()
})
for face in observations
{
let view = self.CreateBoxView(withColor: UIColor.red)
view.frame = self.transformRect(fromRect: face.boundingBox, toViewRect: self.analyzedImageView)
self.analyzedImageView.image = self.originalImageView.image
self.resultImageView.addSubview(view)
}
}
}
//MARK - Instance Methods
func boxView(withColor : UIColor) -> UIView {
let view = UIView()
view.layer.borderColor = withColor.cgColor
view.layer.borderWidth = 2.0
view.backgroundColor = UIColor.clear
return view
}
//Convert Vision Frame to UIKit Frame
func transformRect(fromRect: CGRect , toViewRect :UIView) -> CGRect {
var toRect = CGRect()
toRect.size.width = fromRect.size.width * toViewRect.frame.size.width
toRect.size.height = fromRect.size.height * toViewRect.frame.size.height
toRect.origin.y = (toViewRect.frame.height) - (toViewRect.frame.height * fromRect.origin.y )
toRect.origin.y = toRect.origin.y - toRect.size.height
toRect.origin.x = fromRect.origin.x * toViewRect.frame.size.width
return toRect
}

Related

How to stop multiple instances of AVAudioPlayer?

There are multiple classes in which the AVAudioPlayer? instances are created like so
var player: AVAudioPlayer?
var player2: AVAudioPlayer?
Now is there a way I can stop these multiple instances in a different class , tried to create a function and stop all sounds but it does not work , can any one suggest a way to stop all sounds in app, but also one that I can disable at viewWillappear so its not permanently stop all sound
func stopAllSounds() {
var demoView1 = Demo1ViewController()
var player1 = demoView1.player
var demoView2 = Demo2ViewController()
var player2 = demoView2.player
var demoView3 = Demo3ViewController()
var player3 = demoView3.player
var allPlayer: [AVAudioPlayer?] = [player1, player2, player3]
for stopData in allPlayer {
stopData?.stop()
stopData?.currentTime = 0
}
}
The function I call on viewDidload
func playSound() {
guard let path = Bundle.main.path(forResource: "demosound", ofType:"wav") else {
return }
let url = URL(fileURLWithPath: path)
do {
player = try AVAudioPlayer(contentsOf: url)
player?.volume = 1.0
player?.play()
Utility.avPLayers.append(player!) <—— add the instance
} catch let error {
print(error.localizedDescription)
}
}

iOS Swift - How to get the last items of an array and append it to another array for chat messages

I have the following code (a Parse Server query into a function that gets fired every 20 seconds and checks if there are new rows in the Messages class (table)):
/* Variables */
var messagesArray = [PFObject]()
var theMessages = [PFObject]()
/* func queryMessages() code */
let messId1 = "\(currentUser.objectId!)\(userObj.objectId!)"
let messId2 = "\(userObj.objectId!)\(currentUser.objectId!)"
let predicate = NSPredicate(format:"messageID = '\(messId1)' OR messageID = '\(messId2)'")
let query = PFQuery(className: MESSAGES_CLASS_NAME, predicate: predicate)
query.whereKey(MESSAGES_DELETED_BY, notContainedIn: [currentUser.objectId!])
query.order(byAscending: "createdAt")
query.skip = skip
query.findObjectsInBackground { (objects, error)-> Void in
if error == nil {
for i in 0..<objects!.count { self.messagesArray.append(objects![i]) }
if (objects!.count == 100) {
self.skip = self.skip + 100
self.queryMessages()
} else {
self.messagesArray = objects!
/*test*/
let messDiff = self.messagesArray.count - self.theMessages.count
print("MESSAGES ARRAY: \(self.messagesArray.count)")
print("THE MESSAGES: \(self.theMessages.count)")
print("MESS DIFF: \(messDiff)\n")
if self.theMessages.count < self.messagesArray.count {
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i])
}// ./ For
self.messagesTableView.reloadData()
}// ./ If
// Scroll TableView down to the bottom
if objects!.count != 0 {
Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(self.scrollTableViewToBottom), userInfo: nil, repeats: false)
}
}// ./ If
// error
} else { self.simpleAlert("\(error!.localizedDescription)")
}}
So, the app is running on my device, and with another device I send 2 new messages to the test User on my device. After 20 seconds, a Timer calls the function above and I obviously get this in the console:
MESSAGES ARRAY: 9
THE MESSAGES: 7
MESS DIFF: 2
So, what I would need is to simply append the 2 new items of the messagesArray into the theMessages array, so that when my TableView will reload, it'll just add 2 new rows on its bottom.
Obviously now I get the first 2 messages again because of this code:
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i])
}// ./ For
I cannot figure out what 'workaround' I should use to achieve what I need...
As you are reloading the entire table view without animations anyway why not simply
self.theMessages = self.messagesArray
self.messagesTableView.reloadData()
Or if the new messages are reliably at the end of the array get them with suffix and append them to theMessages
let newMessages = Array(self.messagesArray.suffix(messDiff))
self.theMessages.append(contentsOf: newMessages)
if self.theMessages.count < self.messagesArray.count {
let temp = self.theMessages.count
for i in 0..<messDiff {
self.theMessages.append(self.messagesArray[i+temp])
}// ./ For
self.messagesTableView.reloadData()}
change your code reload data code to this

iOS 11 UINavigationBar bar button items alignment

I am upgrading my application to iOS 11 and I am seeing some problems with the navigation bar, part of my problems have already asked questions here, so I won't mention them in this question.
The particular problem in question is that the navigation bar's bar button items are spaced differently. My main left and right bar button items are closer to the screen's horizontal center now and I can't move them near to the screen edges. In the past I have used a custom UIButton subclass and created bar button items with custom view. The alignment solution were alignmentRectInsets and contentEdgeInsets, now I couldn't manage to produce the expected results using this approach.
Edit:
I have retested with iOS 11 beta 2 and the issue remains.
Edit 2:
I Have retested with iOS beta 3 and the issue remains.
Now in iOS 11 you can manage UINavigationBarContentView to adjust left and right constraints, and UIStackView to adjust buttons (or other elements).
This is my solution for a navigation bar with items on left and right. Also it fixes if you have several buttons together in one side.
- (void) updateNavigationBar {
for(UIView *view in self.navigationBar.subviews) {
if ([NSStringFromClass([view class]) containsString:#"ContentView"]) {
// Adjust left and right constraints of the content view
for(NSLayoutConstraint *ctr in view.constraints) {
if(ctr.firstAttribute == NSLayoutAttributeLeading || ctr.secondAttribute == NSLayoutAttributeLeading) {
ctr.constant = 0.f;
} else if(ctr.firstAttribute == NSLayoutAttributeTrailing || ctr.secondAttribute == NSLayoutAttributeTrailing) {
ctr.constant = 0.f;
}
}
// Adjust constraints between items in stack view
for(UIView *subview in view.subviews) {
if([subview isKindOfClass:[UIStackView class]]) {
for(NSLayoutConstraint *ctr in subview.constraints) {
if(ctr.firstAttribute == NSLayoutAttributeWidth || ctr.secondAttribute == NSLayoutAttributeWidth) {
ctr.constant = 0.f;
}
}
}
}
}
}
}
As you see it is not necessary to add more constraints, as other people have done. There are already defined constraints so they can be changed.
After about two days, here is the simplest and safest solution I could come up with. This solution only works with custom view bar button items, the code for which is included. It is important to note that the left and right margins on the navigation bar have not changed from iOS10 to iOS11 - they are still 16px. Such a large margin makes it difficult to have a sufficiently large click region.
Bar buttons are now layed out with UIStackView's. The prior method of shifting those buttons closer to the margin involved using negative fixed spaces which these stack views cannot handle.
Subclass UINavigationBar
FWNavigationBar.h
#interface FWNavigationBar : UINavigationBar
#end
FWNavigationBar.m
#import "FWNavigationBar.h"
#implementation FWNavigationBar
- (void)layoutSubviews {
[super layoutSubviews];
if (#available(iOS 11, *)) {
self.layoutMargins = UIEdgeInsetsZero;
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:#"ContentView"]) {
subview.layoutMargins = UIEdgeInsetsZero;
}
}
}
}
#end
Using the UINavigationController
#import "FWNavigationBar.h"
UINavigationController *controller = [UINavigationController initWithNavigationBarClass:[FWNavigationBar class] toolbarClass:nil];
[controller setViewControllers:#[rootViewController] animated:NO];
Creating a UIBarButton
Place this code either in a UIBarButton category or in the file you plan on using the bar button which will return an identical looking bar button item using a UIButton.
+ (UIBarButtonItem *)barButtonWithImage:(UIImage *)image target:(id)target action:(SEL)action {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
//Note: Only iOS 11 and up supports constraints on custom bar button views
button.frame = CGRectMake(0, 0, image.size.width, image.size.height);
button.tintColor = [UIColor lightGrayColor];//Adjust the highlight color
[button setImage:image forState:UIControlStateNormal];
//Tint color only applies to this image
[button setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateHighlighted];
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
return [[UIBarButtonItem alloc] initWithCustomView:button];
}
Setting the bar button in your controller
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [UIBarButtonItem barButtonWithImage:[UIImage imageNamed:#"your_button_image"] target:self action:#selector(leftButtonPressed)];
}
Lastly, I would recommend leaving the left and right margins at zero and just adjusting the position of the button within your image. This will allow for you to take advantage of the full clickable region up to the edge of the screen. The same goes for the height of your image - make sure the height is 44 points.
There is good article on this : http://www.matrixprojects.net/p/uibarbuttonitem-ios11/
Using this we can at least push rightbarbuttonitems to right till it leaves 8 pixel margin from trailing of the UINavigationBar.
Explained really well.
I noticed a similar problem.
I reported an Apple Radar for a similar problem we noticed, #32674764 if you want to refer to it, if you create a Radar.
I also created a thread in Apple's forum, but no feedback yet:
https://forums.developer.apple.com/message/234654
I stumbled upon this: UINavigationItem-Margin.
It works like a charm.
Taken from this answer: BarButtons now use Autolayout and thus require constraints.
if #available(iOS 9.0, *) {
cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}
Objective C
if (#available(iOS 9, *)) {
[cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
[cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}
Solution 1:
In light of Apple's response that this is expected behavior, we worked around the problem by removing the toolbar and adding a custom view.
I realize that this may not be possible in all cases, but the regular UIView is much easier to customize to the app's appearance than the toolbar and navigation bar where Apple has control of the button positioning.
Instead of setting our custom button as the custom view of the ui bar button object, it was we set it as a subview of the blank ui buttons in the custom view.
Doing this we were able to return to the same look of our ios 10 app
Solution 2:
A bit messy, we wrapped our custom view button in an outer UIButton so the frame location can be set. This does make the outer left edge of the button un-tappable unfortunately, but corrects the look of the button position. See example:
UIButton* outerButton = [UIButton new]; //the wrapper button
BorderedButton* button = [self initBorderedButton]; //the custom button
[button setTitle:label forState:UIControlStateNormal];
[button setFrame:CGRectMake(-10, 0, [label length] * 4 + 35, 30)];
[button addTarget:controller action:#selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
[outerButton addSubview:button]; //add custom button to outer wrapper button
[outerButton setFrameWidth:button.frameWidth]; //make sure title gives button appropriate space
controller.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:outerButton]; //add wrapper button to the navigation bar
controller.navigationItem.hidesBackButton = YES;
Using that method we keep our navigation bar and can position the button to where it is needed.
Edit: We found that solution 2 does not work on ios 10, this probably will affect only the tiny percent of developers forced to be backward compatible.
Solution 3
What we were really more concerned about with the inward crowding of the buttons was the fact that the title of the navigation bar ran into the custom left button, the size of the margins was less important and used as a tool to make space. The solution is to simply add a spacer item to the left items.
UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:button];
UIBarButtonItem* spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
controller.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:backButton, spacer, nil];
Swift version of thedeveloper3214's custom UINavigationBar subclass' layoutSubviews method(https://stackoverflow.com/a/46660888/4189037):
override func layoutSubviews() {
super.layoutSubviews();
if #available(iOS 11, *){
self.layoutMargins = UIEdgeInsets()
for subview in self.subviews {
if String(describing: subview.classForCoder).contains("ContentView") {
let oldEdges = subview.layoutMargins
subview.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: oldEdges.right)
}
}
}
}
I had the same issue. I had three buttons as right barbutton items on the navigation bar stack view. So, I have updated the insets of the subviews of navigation bar.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
for view in (self.navigationController?.navigationBar.subviews)! {
view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)
}
}
Here 11 is the space which I needed. It can be anything as per your requirement.
Also, if you want the buttons with 0 insets, replace view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11) with view.layoutMargins = UIEdgeInsets.zero
Another answer may be help.
My solution : It works in ios 9 - 12. You should call fixNavigationItemsMargin(margin:) in function viewDidAppear(_ animated: Bool) and viewDidLayoutSubviews(). fixNavigationItemsMargin(margin:) would modify the UINavigationController stack.
you could call fixNavigationItemsMargin(margin:) in BaseNavigationController ,do the common work. And call fixNavigationItemsMargin(margin:) in UIViewController do precise layout.
// do common initilizer
class BaseNavigationController: UINavigationController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
fixNavigationItemsMargin()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fixNavigationItemsMargin()
}
}
// do precise layout
class ViewController: UIViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navigationController?.fixNavigationItemsMargin(40)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.fixNavigationItemsMargin(40)
}
}
extension UINavigationController {
func fixNavigationItemsMargin(_ margin: CGFloat = 8) {
let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
if systemMajorVersion >= 11 {
// iOS >= 11
guard let contentView = navigationBar.subviews
.first(
where: { sub in
String(describing: sub).contains("ContentView")
}) else { return }
// refer to: https://www.matrixprojects.net/p/uibarbuttonitem-ios11/
// if rightBarButtonItems has not any custom views, then margin would be 8(320|375)/12(414)
// should use customView
let needAdjustRightItems: Bool
if let currentVC = viewControllers.last,
let rightItems = currentVC.navigationItem.rightBarButtonItems,
rightItems.count > 0,
rightItems.filter({ $0.customView != nil }).count > 0 {
needAdjustRightItems = true
} else {
print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
needAdjustRightItems = false
}
let needAdjustLeftItems: Bool
if let currentVC = viewControllers.last,
let leftItems = currentVC.navigationItem.leftBarButtonItems,
leftItems.count > 0,
leftItems.filter({ $0.customView != nil }).count > 0 {
needAdjustLeftItems = true
} else {
print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
needAdjustLeftItems = false
}
let layoutMargins: UIEdgeInsets
if #available(iOS 11.0, *) {
let directionInsets = contentView.directionalLayoutMargins
layoutMargins = UIEdgeInsets(
top: directionInsets.top,
left: directionInsets.leading,
bottom: directionInsets.bottom,
right: directionInsets.trailing)
} else {
layoutMargins = contentView.layoutMargins
}
contentView.constraints.forEach(
{ cst in
// iOS 11 the distance between rightest item and NavigationBar should be margin
// rightStackView trailing space is -margin / 2
// rightestItem trailing to rightStackView trailing is -margin / 2
let rightConstant = -margin / 2
switch (cst.firstAttribute, cst.secondAttribute) {
case (.leading, .leading), (.trailing, .trailing):
if let stackView = cst.firstItem as? UIStackView,
stackView.frame.minX < navigationBar.frame.midX {
// is leftItems
if needAdjustLeftItems {
cst.constant = margin - layoutMargins.left
}
} else if let layoutGuide = cst.firstItem as? UILayoutGuide,
layoutGuide.layoutFrame.minX < navigationBar.frame.midX {
// is leftItems
if needAdjustLeftItems {
cst.constant = margin - layoutMargins.left
}
}
if let stackView = cst.firstItem as? UIStackView,
stackView.frame.maxX > navigationBar.frame.midX {
// is rightItems
if needAdjustRightItems {
cst.constant = rightConstant
}
} else if let layoutGuide = cst.firstItem as? UILayoutGuide,
layoutGuide.layoutFrame.maxX > navigationBar.frame.midX {
// is rightItems
if needAdjustRightItems {
cst.constant = rightConstant
}
}
default: break
}
})
// ensure items space == 8, minispcae
contentView.subviews.forEach(
{ subsub in
guard subsub is UIStackView else { return }
subsub.constraints.forEach(
{ cst in
guard cst.firstAttribute == .width
|| cst.secondAttribute == .width
else { return }
cst.constant = 0
})
})
} else {
// iOS < 11
let versionItemsCount: Int
if systemMajorVersion == 10 {
// iOS 10 navigationItem.rightBarButtonItems == 0
// space = 16(320|375) / 20(414)
// should adjust margin
versionItemsCount = 0
} else {
// iOS 9 navigationItem.rightBarButtonItems == 0
// space = 8(320|375) / 12(414)
// should not adjust margin
versionItemsCount = 1
}
let spaceProducer = { () -> UIBarButtonItem in
let spaceItem = UIBarButtonItem(
barButtonSystemItem: .fixedSpace,
target: nil,
action: nil)
spaceItem.width = margin - 16
return spaceItem
}
if let currentVC = viewControllers.last,
var rightItems = currentVC.navigationItem.rightBarButtonItems,
rightItems.count > versionItemsCount,
let first = rightItems.first {
// ensure the first BarButtonItem is NOT fixedSpace
if first.title == nil && first.image == nil && first.customView == nil {
print("rightBarButtonItems SPACE SETTED!!! SPACE: ", abs(first.width))
} else {
rightItems.insert(spaceProducer(), at: 0)
// arranged right -> left
currentVC.navigationItem.rightBarButtonItems = rightItems
}
}
if let currentVC = viewControllers.last,
var leftItems = currentVC.navigationItem.leftBarButtonItems,
leftItems.count > versionItemsCount,
let first = leftItems.first {
if first.title == nil && first.image == nil && first.customView == nil {
print("leftBarButtonItems SPACE SETTED!!! SPACE: ", abs(first.width))
} else {
leftItems.insert(spaceProducer(), at: 0)
// arranged left -> right
currentVC.navigationItem.leftBarButtonItems = leftItems
}
}
}
}
}

Iterate array of SKShapeNodes

Struggling with how to iterate through an array of SKShapeNodes. I seem to be able to go through it in DidMoveToView(), but, not WhenTouchesBegan().
From GameScene.swift:
class GameScene: SKScene {
...
var areaTwo = SKShapeNode()
var areaThree = SKShapeNode()
var areaFour = SKShapeNode()
var currentArea = SKShapeNode()
//CGPaths as UIBezierPaths set here
var areas = [SKShapeNode]()
override func didMoveToView(view: SKView) {
...
areaTwo = SKShapeNode(path: areaTwoPath.CGPath)
areaThree = SKShapeNode(path: areaThreePath.CGPath)
areaFour = SKShapeNode(path: areaFourPath.CGPath)
let areas = [areaTwo, areaThree, areaFour]
...
//this works
for area in areas {
area.lineWidth = 4
addChild(area)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
currentArea.fillColor = UIColor.clearColor()
//this does not work! No errors thrown. Just doesn't seem to do anything.
for area in areas{
currentArea = area
if currentArea.containsPoint(location) {
currentArea.fillColor = UIColor.redColor()
}
}
}
}
What is frustrating is if I use a series of if...else if...else if I can check every single area, but, can't check them through the array.
not quite clear about your target. if you simply want a way to iterate child nodes, you can try
//init child nodes
for i in 1...2{
let areaNode = SKShapeNode()
...
areaNode.name = "area"
parentNode.addChild(areaNode)
}
//iteration
for node in parentNode.children{
if node.name == "area"{
print("here we find a child area")
}else{
print("some irrelevant node found")
}
}
Btw, the reason why your code in didMoveToView() work is you declared an new areas array, which in fact is an in-method variable replaced the role of previous class property areas

Spawning multiple coin node(s) using an array Swift SpriteKit

I use a function (shown below) to spawn a coin node in specific locations at random using an array.
Using this function, I am trying to incorporate more than one coin node (that are slightly different from one another) into this function so that multiple nodes can use this array to spawn and function just like the first coin node.
The problem that I have is that when I incorporate another node into this function or make a new but similar function for the 2nd node I get a Thread 1 SIGABERT error after the game crashes.
I currently have two separate functions for each node that are very similar, but with slight differences to accommodate each node.
func generateCoinZero() {
if(self.actionForKey("spawningCoinZero") != nil){return}
let coinTimerZero = SKAction.waitForDuration(2, withRange: 7)
let spawnCoinZero = SKAction.runBlock {
let coinZeroTexture = SKTexture(imageNamed: "coinZero")
self.coinZero = SKSpriteNode(texture: coinZeroTexture)
self.coinZero.physicsBody = SKPhysicsBody(circleOfRadius: self.coinZero.size.height / 12)
self.coinZero.physicsBody?.dynamic = false
self.coinZero.physicsBody?.allowsRotation = false
self.coinZero.zPosition = 1
self.coinZero.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
self.coinZero.physicsBody?.contactTestBitMask = ColliderType.playerCategory
self.coinZero.physicsBody?.collisionBitMask = 0
self.player.physicsBody?.categoryBitMask = ColliderType.playerCategory
self.player.physicsBody?.contactTestBitMask = 0
self.player.physicsBody?.collisionBitMask = ColliderType.boundary
var coinPositionZero = Array<CGPoint>()
coinPositionZero.append((CGPoint(x:250, y:139)))
coinPositionZero.append((CGPoint(x:790, y:298)))
coinPositionZero.append((CGPoint(x:225, y:208)))
coinPositionZero.append((CGPoint(x:220, y:237)))
let spawnLocationZero = coinPositionZero[Int(arc4random_uniform(UInt32(coinPositionZero.count)))]
let actionZero = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))
self.coinZero.runAction(actionZero)
self.coinZero.position = spawnLocationZero
self.addChild(self.coinZero)
print(spawnLocationZero)
}
let sequenceZero = SKAction.sequence([coinTimerZero, spawnCoinZero])
self.runAction(SKAction.repeatActionForever(sequenceZero), withKey: "spawningCoinZero")
}
func generateCoinTwo() {
if(self.actionForKey("spawnCoinTwo") != nil){return}
let coinTimerTwo = SKAction.waitForDuration(2, withRange: 7)
let spawnCoinTwo = SKAction.runBlock {
let coinTwoTexture = SKTexture(imageNamed: "coinTwo")
self.coinTwo = SKSpriteNode(texture: coinTwoTexture)
self.coinTwo.physicsBody = SKPhysicsBody(circleOfRadius: self.coinTwo.size.height / 12)
self.coinTwo.physicsBody?.dynamic = false
self.coinTwo.physicsBody?.allowsRotation = false
self.coinTwo.zPosition = 1
self.addChild(self.coinTwo)
var coinPositionTwo = Array<CGPoint>()
coinPositionTwo.append((CGPoint(x:250, y:139)))
coinPositionTwo.append((CGPoint(x:790, y:298)))
coinPositionTwo.append((CGPoint(x:225, y:208)))
coinPositionTwo.append((CGPoint(x:220, y:237)))
let spawnLocationTwo = coinPositionTwo[Int(arc4random_uniform(UInt32(coinPositionTwo.count)))]
let actionTwo = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))
self.coinTwo.runAction(actionTwo)
self.coinTwo.position = spawnLocationTwo
self.addChild(self.coinTwo)
print(spawnLocationTwo)
}
let sequenceTwo = SKAction.sequence([coinTimerTwo, spawnCoinTwo])
self.runAction(SKAction.repeatActionForever(sequenceTwo), withKey: "spawnCoinTwo")
}
OK, there are quite a lot of issues here, the main ones being the extreme duplication of code and having your generateCoin...-functions doing way too much. So here goes:
You state in the comments that the scene should have two coins spawning at different times at one of four possible positions. If the scene has two coins, then the scene has two coins. Let's just create these as properties and be done with it:
// Your two coin properties
let coin1 = coinNode()
let coin2 = coinNode()
// the function from which they are created
func coinNode() -> SKSpriteNode {
let coinNode = SKSpriteNode(imageNamed: "coinZero")
coinNode.physicsBody = SKPhysicsBody(circleOfRadius: coinNode.size.height / 2)
coinNode.physicsBody?.dynamic = false
coinNode.physicsBody?.allowsRotation = false
coinNode.zPosition = 1
coinNode.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
coinNode.physicsBody?.contactTestBitMask = ColliderType.playerCategory
coinNode.physicsBody?.collisionBitMask = 0 // A ColliderType.none would be lovely...
return coinNode
}
Now, these coins are not yet added to the scene nor do they have a proper position, this sounds like a fitting scope for another function:
func addCoin() {
let positions = [ CGPoint(x:250, y:139), CGPoint(x:790, y:298), CGPoint(x:225, y:208), CGPoint(x:220, y:237)]
let position = positions[Int(arc4random_uniform(UInt32(positions.count)))]
if coin1.parent == nil {
coin1.position = position
addChild(coin1)
} else if coin2.parent == nil {
coin2.position = position
addChild(coin2)
}
}
Finally you want to have this function being called so do the following in your scene's init or setup:
let delay = SKAction.waitForDuration(1) // or however long you want it to be between each spawn
let addCoinCall = SKAction.runBlock({ self.addCoin() })
let spawnSequence = SKAction.sequence([delay, addCoinCall])
runAction(SKAction.repeatActionForever(spawnSequence))
You can't addChild twice, put addChild out of runBlock, and make sure that you are addChild once.
If you want put multiple coin, is better to copy your node and add on the scene.
You can create a node like coinZero out of function, and then inside the function do something like:
let coinToAdd = coinZero.copy()

Resources