Wondering why when I drag the WhiteDot over the SmallDot it adds more than 1 to my UITextField? It varies from each time I run the iPhone simulator. Sometimes it adds 11, 2, 3, 5...etc. No idea why?
Another question, when the SmallDot disappears I also want it to spawn in a specified view. The Score is in top right corner.
class SecondViewController: UIViewController {
private var AddOne = 0
#IBOutlet weak var Score: UITextField!
}
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
if (WhiteDot.frame.contains(SmallDot.frame)) {
SmallDot.image = nil;
AddOne += 1
Score.text = "\(AddOne)"
}
}
}
You haven't provided much information.
A pan gesture recognizer reports a continuous stream of coordinates as the user pans with their finger. You're probably getting multiple "hits" when you drag one dot over another. You need to have some sort of test to see if the smaller dot has been eaten yet. Since you're already setting the image on the small dot to nil, you could check that:
if (WhiteDot.frame.contains(SmallDot.frame) && SmallDot.image != nil) {
SmallDot.image = nil;
AddOne += 1
Score.text = "\(AddOne)"
}
Note that variable names like whiteDot, smallDot, addOne, and score should start with a lower case letter. Class names and types should start with an upper case letter. This is a strong convention in Swift and you should follow it. The language doesn't force you to follow it, but I wouldn't hire a programmer who didn't.
Related
Creating a 2d game, and want to add moving platforms where I can control the pattern and rotation of the platform.
Example:
I want this platform to move in a clockwise motion, also when it reaches the top and bottom I want it to rotate accordingly as if it is facing the direction it is going (so the platform would essentially rotate 180 degrees as it arches at the top and bottom).
I can't use SKActions because I need the physics to work properly.
My idea is that I can use an Agent with behaviors and goals to do this. Not sure if I will need path finding as well.
I'm researching how to use these features, but the documentation and lack of tutorials is hard to decipher. I'm hoping someone could save my some time of trial and error by providing an example of how this would work.
Thanks in advance!
Using SKActions or adjusting the position manually will be sucky to get working the way you want, BUT it's always worth a shot to give it a go, since it would take 2 minutes to mock it up and see...
I'd suggest doing something like a Path class that sends velocity commands to the platform every frame ...
Actually, this could be a good exercise in learning the state machine..
MOVE RIGHT STATE: if X position > starting position X + 200, enter state "move down"
MOVE DOWN STATE: if Y position < starting position Y - 200, enter state "move left"
MOVE LEFT STATE: if X position < starting position X, enter state "move up"
MOVE UP STATE: if Y position > starting position Y, enter state "move right"
.. there are ways you could figure out to give it more curvature instead of just straight angle (when changing direction)
Otherwise you would have to translate that into a class / struct / component and give each platform it's own instance of it.
///
Another option is to take the physics out of the equation, and create a playerIsOnPlatform property... then you manually adjust the position of the player each frame... (or maybe a SKConstraint)
This would then require more code on jumping and such, and turns things to spaghetti pretty quickly (last time I tried it)
But, I was able to successfully clone this, using proper hit detection going that route:
https://www.youtube.com/watch?v=cnhlFZeIR7Q
UPDATE:
Here is a working project:
https://github.com/fluidityt/exo2/tree/master
Boilerplate swift stuff:
import SpriteKit
import GameplayKit
// Workaround to not having any references to the scene off top my head..
// Simply add `gScene = self` in your didMoveToViews... or add a base scene and `super.didMoveToView()`
var gScene = GameScene()
class GameScene: SKScene {
override func didMove(to view: SKView) {
gScene = self
}
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
func gkUpdate(_ currentTime: TimeInterval) -> TimeInterval {
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
let dt = currentTime - self.lastUpdateTime
for entity in self.entities {
entity.update(deltaTime: dt)
}
return currentTime
}
override func update(_ currentTime: TimeInterval) {
self.lastUpdateTime = gkUpdate(currentTime)
}
}
Here is the very basic component:
class Platforms_BoxPathComponent: GKComponent {
private enum MovingDirection: String { case up, down, left, right }
private var node: SKSpriteNode!
private var state: MovingDirection = .right
private lazy var startingPos: CGPoint = { self.node.position }()
#GKInspectable var startingDirection: String = "down"
#GKInspectable var uniqueName: String = "platform"
#GKInspectable var xPathSize: CGFloat = 400
#GKInspectable var yPathSize: CGFloat = 400
// Moves in clockwise:
private var isTooFarRight: Bool { return self.node.position.x > (self.startingPos.x + self.xPathSize) }
private var isTooFarDown: Bool { return self.node.position.y < (self.startingPos.y - self.yPathSize) }
private var isTooFarLeft: Bool { return self.node.position.x < self.startingPos.x }
private var isTooFarUp: Bool { return self.node.position.y > self.startingPos.y }
override func didAddToEntity() {
print("adding component")
// can't add node here because nodes aren't part of scene yet :(
// possibly do a thread?
}
override func update(deltaTime seconds: TimeInterval) {
if node == nil {
node = gScene.childNode(withName: uniqueName) as! SKSpriteNode
// for some reason this is glitching out and needed to be redeclared..
// showing 0 despite clearly not being 0, both here and in the SKS editor:
xPathSize = 300
}
let amount: CGFloat = 2 // Amount to move platform (could also be for for velocity)
// Moves in clockwise:
switch state {
case .up:
if isTooFarUp {
state = .right
fallthrough
} else { node.position.y += amount }
case .right:
if isTooFarRight {
state = .down
fallthrough
} else { node.position.x += amount }
case .down:
if isTooFarDown {
state = .left
fallthrough
} else { node.position.y -= amount }
case .left:
if isTooFarLeft {
state = .up
fallthrough
} else { node.position.x -= amount }
default:
print("this is not really a default, just a restarting of the loop :)")
node.position.y += amount
}
}
}
And here is what you do in the sks editor:
Trying to get the code so when if whiteDotDist < centerRadius - whiteDotRadius is executed all the code below it is active, and when the code below it is executed it becomes inactive again until the if whiteDotDist < centerRadius - whiteDotRadius is executed again. Sort of like a loop, so you have to keep going back and fourth from center to smallDot. Hard to explain over computer. Update it is giving me error 'Binary operator '<' cannot be applied to operands of type 'CGFloat' and 'Double'
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
let centerRadius = 37.5
let whiteDotRadius = 23.5
let whiteDotDist = hypot(center.center.x - whiteDot.center.x, center.center.y - whiteDot.center.y - whiteDot.center.y)
if whiteDotDist < centerRadius - whiteDotRadius {
resetTimer() }
if (whiteDot.frame.contains(smallDot.frame) && smallDot.image != nil) {
addOne += 1
score.text = "\(addOne)"
resetTimer()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(SecondViewController.startTimer), userInfo: nil, repeats: true)
smallDot.center = spawnRandomPosition()
}
}
}
Check to make sure:
All of the views have the same parent (so that they are all in the same coordinate system)
The Frames are tight around the circles. Change the background color of center, whiteDot, and smallDot to Red and post a picture
Even if you do that, your code checks if the bounding rects are inside each other, so it may look like the smallDot is outside the whiteDot (if it were in the corner), but the bounding frame is enclosed by whiteDot's frame.
If you want to check that the circles (not bounding boxes) are inside each other, get the distance between centers and make sure that that is within the (outer radius - smaller dot radius).
pseudo-code
let centerRadius = 100 // set this to radius of center circle
let whiteDotRadius = 10 // set this to whiteDot radius
let whiteDotDist = hypotf(center.center.x - whiteDot.center.x, center.center.y - whiteDot.center.y)
if whiteDotDist < centerRadius - whiteDotRadius {
// whiteDot is inside center
}
I am rather new to iOS and swift 3 programming. At the moment I am working on a little learning project. I have the following problem.
On my screen I have 4 UIViews and 4 UIImageViews. The app allows to “drag and drop” an image onto one of the UIViews. This works pretty well so far. I use Pan Gesture Recognisers for the dragging of UIImages. But for some reason one of the UIViews seems to be in front of the image, meaning that I can drop the image to this area, but I cannot see it - it is layered under the UIView. The other three behave properly, the UIImageView is layered on top of the UIView. In the viewDidLoad() function of the view controller I set the background color of the UIViews to grey. If I don’t do that, the image will be displayed even on the fourth UIView.
I have checked for differences between the four UIView objects but cannot find any. I also tried to recreate the UIView by copying it from one of the the other three.
So my question is: Is there any property which defines that the UIView is layered on top or can be sent to back so that other objects are layered on top of it? Or is there anything else I am missing? Any hints would be very appreciated.
I was not quite sure which parts of the code are relevant, so I posted some of it (but still guess that this is rather a property of the UIView…)
This is where I set the background of the UIViews
override func viewDidLoad() {
super.viewDidLoad()
lbViewTitle.text = "\(viewTitle) \(level) - \(levelPage)"
if (level==0) {
print("invalid level => EXIT")
} else {
loadData(level: level)
originalPositionLetter1 = letter1.center
originalPositionLetter2 = letter2.center
originalPositionLetter3 = letter3.center
originalPositionLetter4 = letter4.center
dropArea1.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea2.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea3.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea4.layer.backgroundColor = UIColor.lightGray.cgColor
}
}
This is the code that moves the image:
#IBAction func hadlePan1(_ sender: UIPanGestureRecognizer) {
moveLetter(sender: sender, dropArea: dropArea4, movedImage: letter1, originalPosition: originalPositionLetter1)
}
func moveLetter(sender: UIPanGestureRecognizer, dropArea : UIView, movedImage : UIImageView, originalPosition : CGPoint) {
let translation = sender.translation(in: self.view)
if let view = sender.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
if sender.state == UIGestureRecognizerState.ended {
let here = sender.location(in: self.view)
if (dropArea.frame.contains(here)) {
movedImage.center = dropArea.center
} else {
movedImage.center = originalPosition
}
}
}
sender.setTranslation(CGPoint.zero, in: self.view)
}
There are two methods that may be useful to you: UIView.sendSubview(toBack:) and UIView.bringSubview(toFront:).
What I think you should do is to call bringSubview(toFront:) when you start dragging the image:
func moveLetter(sender: UIPanGestureRecognizer, dropArea : UIView, movedImage : UIImageView, originalPosition : CGPoint) {
self.view.bringSubview(toFront: movedImage) // <--- add this line!
let translation = sender.translation(in: self.view)
if let view = sender.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
if sender.state == UIGestureRecognizerState.ended {
let here = sender.location(in: self.view)
if (dropArea.frame.contains(here)) {
movedImage.center = dropArea.center
} else {
movedImage.center = originalPosition
}
}
}
sender.setTranslation(CGPoint.zero, in: self.view)
}
Thanks for reading.
My program adds UIImageViews upon tapping inside a UICollectionViewCell. Once the image is added, there is a separate, static UIButton set to delete any image(s) which intersect the button. The deleting code works fine, but the problem is that the button is not clickable under the programatically-added UIImageView. (If I click on a portion of the button which is not covered by the UIImageView, the button's function is called and the image(s) are properly deleted.)
Edit: I move the added image views around the screen using a UIGestureRecognizer and the handlepan function.
I have tried view.bringSubviewToFront(UIButton). After doing this, the button worked fine, but now the button was over the UIImageView, so I couldn't move the image outside of the button.
Edit 2: I found a workaround which bypasses the button functionality all together and simply deletes the image if its view intersects the button view at the end of the pan gesture.
//I added this code to the end of the handlepan function (and made a function calleddeleteimage which performs the actions of delete image):
if recognizer.state == UIGestureRecognizerState.Ended {
calleddeleteimage()
}
//Here is my original code for the UIPanGestureRecognizer and for the UIButton:
func handlepan(recognizer: UIPanGestureRecognizer) {
let movingview = recognizer.view!
let translation = recognizer.translationInView(recognizer.view)
view.bringSubviewToFront(movingview)
movingview.center = CGPoint(x: movingview.center.x + translation.x, y: movingview.center.y + translation.y)
recognizer.setTranslation(CGPointZero, inView: recognizer.view)
view.bringSubviewToFront(hungryman)
}
#IBOutlet weak var hungryman: UIButton!
#IBAction func deleteimage(sender: AnyObject) {
var count = 0
indexestodelete = []
for i in unlockeddisplaying {
if i.frame.intersects(hungryman.frame) {
i.removeFromSuperview()
indexestodelete.append(count)
}
count = count + 1
}
count = 0
for i in indexestodelete {
unlockeddisplaying.removeAtIndex(i - count)
unlockeddisplayingtypes.removeAtIndex(i - count)
count = count + 1
}
}
Thanks!
TL;DR: How to make UIButton always clickable, even when hidden under UIImageView?
On your image view, set userInteractionEnabled to false and touches will pass through it.
I'm having an issue with some animations in a Swift iOS application. I am trying to allow a user to grab a UIImageView and drag (pan) it to a different point. Then if they push "animate" it shows the animation of the imageview along a path from the first point to the second.
Here is what I have so far, which is more so me just trying to hammer an early solution. I'm getting an error when the "animate" button is pressed that says:
CGPathAddLineToPoint(CGMutablePathRef, const CGAffineTransform *,
CGFloat, CGFloat): no current point.
Here is my code:
// There was some global stuff set up earlier, such as pathPlayer1 which is an
// array of CGPoints I am using to store the path; they are commented
override func viewDidLoad() {
super.viewDidLoad()
var panRecognizer1 = UIPanGestureRecognizer(target: self, action: "handlePanning1:")
playerWithBall.addGestureRecognizer(panRecognizer1)
pathPlayer1.append(playerWithBall.center)
}
func handlePanning1(recognizer: UIPanGestureRecognizer) {
var newTranslation: CGPoint = recognizer.translationInView(playerWithBall)
recognizer.view?.transform = CGAffineTransformMakeTranslation(lastTranslation1.x + newTranslation.x, lastTranslation1.y + newTranslation.y)
if recognizer.state == UIGestureRecognizerState.Ended {
// lastTranslation1 is a global
lastTranslation1.x += newTranslation.x
lastTranslation1.y += newTranslation.y
// another global to get the translation from imageview center in main view
// to the new point in main view
playerWithBallPos.x = playerWithBall.center.x + lastTranslation1.x
playerWithBallPos.y = playerWithBall.center.y + lastTranslation1.y
// add this point to the path to animate along
pathPlayer1.append(playerWithBallPos)
//This was to make sure the append was working
println(pathPlayer1)
}
}
#IBAction func animatePlay(sender: UIButton) {
var path = CGPathCreateMutable()
var i: Int = 0
for (i = 0; i < pathPlayer1.count; i++) {
var location: CGPoint! = pathPlayer1[i]
// I think if its the first time you need to call CGPathMoveToPoint?
if firstTime {
CGPathMoveToPoint(path, nil, location.x, location.y)
firstTime = false
} else {
CGPathAddLineToPoint(path, nil, location.x, location.y)
}
}
var pathAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "pos")
pathAnimation.path = path
pathAnimation.duration = 1.0
}
The panning is working just fine and it appears to be correctly getting the new point, but I have no experience using objective-c and a computer science class worth of knowledge of swift/iOS and I'm not familiar with these types of animations.
I would like to make my solution work so that I could extend it from a single imageview to multiple and animate each one simultaneously (think of a sports playbook and animating a play or something like that)