Method collectionView (didSelectItemAt...) doesn't Work at iTunesConnect; However, It does Work on Debug - swift4.2

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.

Related

SwiftUI - Memory Management

OK, so I'm looking to improve memory within my app.
I have enabled Live Memory Allocation for my project and I'm using the Debug Graph Tool. I'm looking at the backtraces, and coming across issues, which in total honesty, does not make sense to me. I've, to the best of my knowledge, removed strong references, but I am getting issues with parts of my code where I just don't understand/see the issue. And example of this is:
struct ProductScrollView: UIViewRepresentable {
private let view = UIScrollView()
func makeCoordinator() -> Coordinator {
return ProductScrollView.Coordinator(parent1: self)
}
#State var currentPage: Int
#Binding var products: [ProductModel]
func makeUIView(context: Context) -> UIScrollView {
if view.superview == .none {
let childView = UIHostingController(rootView: ProductCell(products: $products, currentPage: currentPage, pageCount: products.count)
) <---- Debugger seems to indicate this with the message "Thread 1"
childView.view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
view.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
view.addSubview(childView.view)
view.showsVerticalScrollIndicator = false
view.showsHorizontalScrollIndicator = false
view.contentInsetAdjustmentBehavior = .never
view.isPagingEnabled = true
view.delegate = context.coordinator
}
return view
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
uiView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
for i in 0..<uiView.subviews.count {
uiView.subviews[i].frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * CGFloat((products.count)))
}
}
class Coordinator: NSObject, UIScrollViewDelegate {
var parent: ProductScrollView
init(parent1: ProductScrollView){
parent = parent1
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.y / UIScreen.main.bounds.height)
parent.currentPage = index
}
deinit {
print("Coordiante scroll view desroted")
}
}
}
Which doesn't make complete sense to me, however, I believe the line:
return ProductScrollView.Coordinator(parent1: self)
May be the issue?
I'll even have the debugger point to functions that are performed on my onAppear method.
Could someone please help as to what I can do to better understand these issues and eliminate them?
Edit -
To delve even further, this line appears in my backtrace:
static var FEEDBACK = FeedbackAPI()
Is it due to creating a new instance everytime?
By design we need to instantiate view inside makeView, so SwiftUI manages UIView lift-cycle to be the same as representable view, like
struct ProductScrollView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
return ProductScrollView.Coordinator(parent1: self)
}
#State var currentPage: Int
#Binding var products: [ProductModel]
func makeUIView(context: Context) -> UIScrollView {
let view = UIScrollView() // << here !!
// ... other code
}

SwiftUI MVVM #Environment Breaks

I'm trying to do a fairly simple View with MVVM to be a good ViewModel citizen. However,
the code breaks while accessing the #Enviromnent Core Data in the ViewModel. I created
two functions in the ViewModel. One accesses Core Data through the #Environment and one
accesses Core Data with the old style - get a reference to AppDelegate and do my own
thing. The OldSchool method works. Comment 2 below. The #Environment does not - it breaks
at the line indicated below with an error that is not helpful for me. Comment 1.
(Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
Now if I take the same #Environment code and put it directly into the view it Works.
And if I call the same line that breaks the MVVM in a Text in the View I get the
correct response. Comment 2
This is the view:
struct UserUtilities: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: DermPhoto.getAllDermPhotos()) var dermPhotos: FetchedResults<DermPhoto>
#State private var reminderInterval = 1
#State private var enableReminders = true
#State private var daysSincePhoto: Int = 0
#ObservedObject var userUtilitiesVM = UserUtilitiesViewModel()
var body: some View {
NavigationView {
VStack {
Group { //group 1
Toggle(isOn: $enableReminders) {
Text("Enable Reminders")
}
.padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
Text("Reminders are" + (enableReminders == true ? " On" : " Off"))
Spacer()
//Comment 3 - this always works
Text("String interpolation of self.dermPhotos.count")
Text("\(self.dermPhotos.count)")
} //group 1
Group { //group 2
Text("It has been " + "\(self.daysSincePhoto) " + (daysSincePhoto == 1 ? "day" : "days") + " since a photo was added.")
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
//options for the sentence above
//\(self.userUtilitiesVM.getTheDateInterval())
//\(self.userUtilitiesVM.getFromEnvironment())
//\(self.userUtilitiesVM.dateInterval)
Spacer()
Stepper("Reminder Interval", value: $reminderInterval, in: 1 ... 30)
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
Text("Reminder Interval is: \(reminderInterval)" + (reminderInterval == 1 ? " day" : " days"))
Spacer()
}//group 2
}
.navigationBarTitle("Reminder Days", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
print("getting the date interval from the nav button")
//self.daysSincePhoto = self.userUtilitiesVM.getOldSchool()
self.daysSincePhoto = self.userUtilitiesVM.getFromEnvironment()
//self.daysSincePhoto = self.getFromEnvironment()
} , label: { Text("Fetch")
}))
}
}
//this always works
func getFromEnvironment() -> Int {
let numberOfRecords = self.dermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = self.dermPhotos.last?.addDate
//ok to bang - addDate is always added to core data
let dateInterval = DateInterval(start: lastDate!, end: now)
let days = Int(dateInterval.duration) / (24 * 3600)
self.daysSincePhoto = days
return days
}
return 0
}
}
And this is the ViewModel:
class UserUtilitiesViewModel: ObservableObject {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: DermPhoto.getAllDermPhotos()) var dermPhotos: FetchedResults<DermPhoto>
#Published var dateInterval: Int = 50
//Comment 2 this always works
func getOldSchool() -> Int {
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
let context = kAppDelegate.persistentContainer.viewContext
var resultsDermPhotos : [DermPhoto] = []
let fetchRequest: NSFetchRequest<DermPhoto> = DermPhoto.fetchRequest() as! NSFetchRequest<DermPhoto>
let sortDescriptor = NSSortDescriptor(key: "addDate", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
resultsDermPhotos = try context.fetch(fetchRequest)
} catch {
print("the fetchRequest error is \(error.localizedDescription)")
}
let numberOfRecords = resultsDermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = resultsDermPhotos.last?.addDate
//ok to bang this since addDate is always added to core data
let di = DateInterval(start: lastDate!, end: now)
let days = Int(di.duration) / (24 * 3600)
dateInterval = days
return days
}
return 0
}
//this never works
func getFromEnvironment() -> Int {
//Comment 1 - this breaks
let numberOfRecords = self.dermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = self.dermPhotos.last?.addDate
//ok to bang this since addDate is always added to core data
let di = DateInterval(start: lastDate!, end: now)
let days = Int(di.duration) / (24 * 3600)
dateInterval = days
return days
}
return 0
}
}
Clearly I can use old school or abandon the ViewModel idea but I would like to know how to fix this. Any guidance would be appreciated. Xcode 11.3 (11C29)
I don't think you can access #Environment from outside View hierarchy.
Your view model does not conform to view. But it won't be view model once it conforms to it.
=> you should abandon view model idea

building project from a tutorial in xcode 7 swift 2, encountering unresolved identifier

I am following a tutorial written in Swift 2 for Xcode 7, part 1 of which (you can navigate to part IV, where my issue has come up) is here: http://www.mav3r1ck.io/spritekit-with-swift/
I am using my own sprites in place of those in the tutorial. When I run my code, an error appears on the first line of the following
let spawnRandomHead = SKAction.runBlock(spawnHead)
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
The full code is here:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
enum bitMask: UInt32 {
case defender = 1
case head = 2
case frame = 4
}
let defender = SKSpriteNode(imageNamed: "Ivanovic is a boss")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = UIColor.blueColor()
defender.position = CGPoint(x: frame.size.width / 2, y:frame.size.height / 2)
defender.physicsBody = SKPhysicsBody(texture: defender.texture!, size: defender.frame.size)
defender.physicsBody?.dynamic = false
defender.physicsBody?.affectedByGravity = false
defender.physicsBody?.allowsRotation = false
defender.physicsBody?.categoryBitMask = bitMask.head.rawValue
defender.physicsBody?.contactTestBitMask = bitMask.head.rawValue
defender.physicsBody?.collisionBitMask = 0
addChild(defender)
let spawnRandomHead = SKAction.runBlock(spawnHead)
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVectorMake(0.0, -0.9)
defender.physicsBody?.contactTestBitMask = bitMask.frame.rawValue
defender.physicsBody?.collisionBitMask = bitMask.frame.rawValue
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
//print(touchLocation)
let moveTo = SKAction.moveTo(touchLocation, duration: 1.0)
defender.runAction(moveTo)
func randomNumber(min min: CGFloat, max: CGFloat) -> CGFloat {
let random = CGFloat(Float(arc4random()) / 0xFFFFFFFF)
return random * (max - min) + min
}
func spawnHead() {
let head = SKSpriteNode(imageNamed: "The Biter Strikes")
head.position = CGPoint(x: frame.size.width * randomNumber(min: 0, max: 1), y: frame.size.height + head.size.height)
head.physicsBody = SKPhysicsBody(texture: head.texture!, size: head.frame.size)
head.physicsBody?.categoryBitMask = bitMask.head.rawValue
head.physicsBody?.contactTestBitMask = bitMask.defender.rawValue
addChild(head)
}
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask) {
case bitMask.defender.rawValue | bitMask.head.rawValue:
let secondNode = contact.bodyB.node
secondNode?.physicsBody?.allowsRotation = true
let firstNode = contact.bodyA.node
firstNode?.physicsBody?.allowsRotation = true
firstNode?.removeFromParent()
default:
return
}
}
}
}
I have tried cleaning & rebuilding, restarting Xcode, and moving sections of the code around, but the error does not go away. I appreciate your support!
Hmm. Tried both. Now on the second line this
let spawnRandomHead = SKAction.runBlock({ [unowned self] () -> Void in
self.spawnHead()
})
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
a new error pops up saying " Value of type 'GameScene' has no member 'spawnHead' ".
The runBlock requires closure as an argument, so replace
let spawnRandomHead = SKAction.runBlock(spawnHead)
with
let spawnRandomHead = SKAction.runBlock({ [unowned self] () -> Void in
self.spawnHead()
})
or simply
let spawnRandomHead = SKAction.runBlock { [unowned self] in
self.spawnHead()
}

Saved Parse Database into an array called recipes and am now trying to access content within that array

I have a database in parse that i have pulled into a swift array. The custom parse object is called UserRecipe. The array is called recipes and is located in the viewDidLoad method. I am trying to set the imageview i have called recipeImage, to always access the image of the first element in the array. I do this in the updateImage function but am not sure if I have the correct syntax. Also the array seems to be stored only with the viewDidLoad method and is not accessible to my updateImage function. I'm wondering how to make it global so all functions can access it. Thanks in advance for any help.
The database looks like this:
import UIKit
import Parse
//import ParseFacebookUtilsV4
import FBSDKCoreKit
import FBSDKLoginKit
class ViewController: UIViewController {
let recipes = [PFObject]?.self
#IBOutlet var recipeImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
//load in all data from Parse custom Object UserRecipe and store it in variable recipes
var query = PFQuery(className:"UserRecipe")
query.findObjectsInBackgroundWithBlock {
(recipes: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let recipes = recipes {
for recipe in recipes {
print(recipe["recipeName"])
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
recipeImage.addGestureRecognizer(gesture)
//let tapping = UITapGestureRecognizer(target: self, action: Selector("wasTapped:"))
//recipeImage.addGestureRecognizer(tapping)
recipeImage.userInteractionEnabled = true
//updateImage()
//getUserInfo()
}
func wasDragged(gesture: UIPanGestureRecognizer) {
//Dragging Animation
let translation = gesture.translationInView(self.view)
let imageDrag = gesture.view!
imageDrag.center = CGPoint(x: self.view.bounds.width / 2 + translation.x, y: self.view.bounds.height / 2 + translation.y - 153)
let xFromCenter = imageDrag.center.x - self.view.bounds.width / 2 + translation.x
let scale = min(100 / abs(xFromCenter), 1)
var rotation = CGAffineTransformMakeRotation(xFromCenter / 200)
var stretch = CGAffineTransformScale(rotation, scale, scale)
imageDrag.transform = stretch
//determines whether current user has accepted or rejected certain recipes
if gesture.state == UIGestureRecognizerState.Ended {
var acceptedOrRejected = ""
if imageDrag.center.x < 100 {
acceptedOrRejected = "rejected"
print("not chosen")
//print("not chosen" + object["recipeName"])
} else if imageDrag.center.x > self.view.bounds.width - 100 {
acceptedOrRejected = "accepted"
print("Chosen")
}
/*if acceptedOrRejected != "" {
PFUser.currentUser()?.addUniqueObjectsFromArray([displayedUserId], forKey: acceptedOrRejected)
PFUser.currentUser()?.saveInBackgroundWithBlock({
(succeeded: Bool, error: NSError?) -> Void in
if succeeded {
} else {
print(error)
}
})
}*/
//Resets image position after it has been let go of
rotation = CGAffineTransformMakeRotation(0)
stretch = CGAffineTransformScale(rotation, 1, 1)
imageDrag.transform = stretch
imageDrag.center = CGPoint(x: self.view.bounds.width / 2, y: self.view.bounds.height / 2 - 153)
updateImage()
}
}
func updateImage() {
recipeImage.image = recipes["image"][0]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am not across Parse but if I were you these are things I would try,
Set a breakpoint in
func updateImage() {
// set breakpoint here and check whether recipes contains any data.
recipeImage.image = recipes["image"][0]
}
Replace the code as shown below,
//replace
recipes["image"][0]
//to
recipes[0]["image"]

removeFromParent() does not remove a node, but makes it invisible

I have a restartButton that must appear when two bodies collide and when it happens for the first time, all goes great - bodies collide--> restartButton appears--> I restart level by touching restartButton.
It was a "good, right restart" And here problem starts...
After level been restarted, if I touch at center of the screen(where restartButton must appear when is called) game crashes, saying following:
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: name:'(null)' particleTexture: 'enemyPart.png' (100 x 100) position:{721.33929, 175.39999} accumulatedFrame:{{inf, inf}, {inf, inf}}'
** First throw call stack:
(0x2a0fa137 etc.)
libc++abi.dylib: terminating with uncaught exception of type NSException"
but restartButton is invisible and it couldn't even be there because no bodies have collided.
If after that "good restart" some enemy collide with player, restartButton appears for a moment and player, enemy1, enemy2, enemy3 are fadingOut from scene.
I would appreciate if someone can help
Here is code where you can see all that stuff:
import SpriteKit
import UIKit
let player = SKEmitterNode(fileNamed: "playerPart.sks")
let enemy1 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy2 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy3 = SKEmitterNode(fileNamed: "ePart.sks")
let restartButton = SKSpriteNode(imageNamed: "restartButton")
let playerCat: UInt32 = 0x1 << 0
let enemyCat: UInt32 = 0x1 << 1
class Level2: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
initWorld()
movements()
player.physicsBody = SKPhysicsBody(circleOfRadius: 50)
player.position = CGPointMake(819.2 , 693.8)
player.zPosition = 1
player.physicsBody?.categoryBitMask = playerCat
player.physicsBody?.contactTestBitMask = enemyCat
player.targetNode = self
self.addChild(player)
enemy1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy1.position = CGPointMake(819.2, 175.4)
enemy1.zPosition = 1
enemy1.physicsBody?.affectedByGravity = false
enemy1.physicsBody?.dynamic = true
enemy1.physicsBody?.allowsRotation = false
enemy1.physicsBody?.categoryBitMask = enemyCat
enemy1.physicsBody?.contactTestBitMask = playerCat
enemy1.physicsBody?.collisionBitMask = 0x0
enemy1.targetNode = self
enemy1.particleBirthRate = 150
enemy1.particleLifetime = 10
enemy1.particleLifetimeRange = 20
enemy1.particlePositionRange = CGVectorMake(50, 60)
enemy1.emissionAngle = 0
enemy1.emissionAngleRange = 0
enemy1.particleSpeed = 0
enemy1.particleSpeedRange = 0
enemy2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy2.position = CGPointMake(614.4, 386.6)
enemy2.zPosition = 1
enemy2.physicsBody?.affectedByGravity = false
enemy2.physicsBody?.dynamic = true
enemy2.physicsBody?.allowsRotation = false
enemy2.physicsBody?.categoryBitMask = enemyCat
enemy2.physicsBody?.contactTestBitMask = playerCat
enemy2.physicsBody?.collisionBitMask = 0x0
enemy2.targetNode = self
enemy2.particleBirthRate = 150
enemy2.particleLifetime = 10
enemy2.particleLifetimeRange = 20
enemy2.particlePositionRange = CGVectorMake(50, 60)
enemy2.emissionAngle = 0
enemy2.emissionAngleRange = 0
enemy2.particleSpeed = 0
enemy2.particleSpeedRange = 0
enemy3.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy3.position = CGPointMake(409.6, 181.8)
enemy3.zPosition = 1
enemy3.physicsBody?.affectedByGravity = false
enemy3.physicsBody?.dynamic = true
enemy3.physicsBody?.allowsRotation = false
enemy3.physicsBody?.categoryBitMask = enemyCat
enemy3.physicsBody?.contactTestBitMask = playerCat
enemy3.physicsBody?.collisionBitMask = 0x0
enemy3.targetNode = self
enemy3.particleBirthRate = 150
enemy3.particleLifetime = 10
enemy3.particleLifetimeRange = 20
enemy3.particlePositionRange = CGVectorMake(50, 60)
enemy3.emissionAngle = 0
enemy3.emissionAngleRange = 0
enemy3.particleSpeed = 0
enemy3.particleSpeedRange = 0
func initWorld() {
self.addChild(enemy1)
self.addChild(enemy2)
self.addChild(enemy3)
}
func movements() {
let move11 = SKAction.moveTo(CGPointMake(819.2, 386.6), duration: 1.5)
let move12 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
let move13 = SKAction.moveTo(CGPointMake(614.4, 175.4), duration: 1.5)
let move14 = SKAction.moveTo(CGPointMake(819.2, 175.4), duration: 1.5)
let enemy1m = SKAction.sequence([move11, move12, move13, move14])
let enemy1move = SKAction.repeatActionForever(enemy1m)
let move21 = SKAction.moveTo(CGPointMake(614.4, 591.4), duration: 1.5)
let move22 = SKAction.moveTo(CGPointMake(409.6, 591.4), duration: 1.5)
let move23 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
let move24 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
let enemy2m = SKAction.sequence([move21, move22, move23, move24])
let enemy2move = SKAction.repeatActionForever(enemy2m)
let move31 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
let move32 = SKAction.moveTo(CGPointMake(204.8, 386.6), duration: 1.5)
let move33 = SKAction.moveTo(CGPointMake(204.8, 181.8), duration: 1.5)
let move34 = SKAction.moveTo(CGPointMake(409.6, 181.8), duration: 1.5)
let enemy3m = SKAction.sequence([move31, move32, move33, move34])
let enemy3move = SKAction.repeatActionForever(enemy3m)
enemy1.runAction(enemy1move)
enemy2.runAction(enemy2move)
enemy3.runAction(enemy3move)
}
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (playerCat | enemyCat) {
self.removeAllActions()
self.runAction(SKAction.waitForDuration(0.1), completion: {
self.runAction(SKAction.waitForDuration(0.2), completion:
{self.removeAllActions()
self.removeChildrenInArray([enemy1, enemy2, enemy3, player])})
restartButton.size = CGSizeMake(200, 200)
restartButton.position = CGPointMake(512, 384)
restartButton.zPosition = 1
self.addChild(restartButton)
})
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location)) {
restartButton.runAction(fadeAway)
restartButton.removeFromParent()
println(1)
self.runAction(SKAction.waitForDuration(1.5), completion: {
let repeatLevel = SKTransition.fadeWithDuration(2)
let level2 = Level2(fileNamed: "Level2")
self.view?.presentScene(level2, transition: repeatLevel)
})
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Your restartButton is having its size set for the first time when your playerCat and enemyCat first collide, which is fine.
Since the restartButton now has a size. You can check if your touch is within the bounds of the restartButton.
if (restartButton .containsPoint(location)) {
Like you have done so, but at no point do you check wether the restartButton is added to the scene.
A quick fix could possibly be:
if (restartButton.parent != nil && restartButton .containsPoint(location)) {
If dont specifically need to check if it with the bounds of the node. You could directly check using this instead. Which will eliminate the need to check for a parent.
if (self.nodeAtPoint(location) == restartButton) {
Another thing i noticed, in your collision detection, you never check if it has already collided. So you might run the same code multiple times, where you just keep removing all actions and then adding a new one.
You could add a simple have variable to prevent redudancy
var detectionMade = false
and reset at
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
initWorld()
movements()
detectionMade = false
...
and set to true when first colliding and checking
if collision == (playerCat | enemyCat) && !detectionMade {
detectionMade = true
...
#martinmeincke I've done it! By doing THIS:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location) && restartButton.parent == nil) {
restartButton.runAction(fadeAway)
println(1)
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location)) {
restartButton.removeFromParent()
self.runAction(SKAction.waitForDuration(1.5), completion: {
let repeatLevel = SKTransition.fadeWithDuration(2)
let level2 = Level2(fileNamed: "Level2")
self.view?.presentScene(level2, transition: repeatLevel)
})
}
}
}

Resources