Why can't I call my function with gestureTapRecogniser - ios

I have this simple function that when someone taps the screen, the code inside the function executes, I know the code in the function works and I get no error when it is typed.
func reload(gestureRecognizer: UITapGestureRecognizer) {
let skView = self.view!
skView.presentScene(scene)
}
I have this if statement, and all the code work except for when I ry to run the function after a certain event has occurred. In the line where I try and call 'reload()' I get the error 'Use of unresolved identifier reload'. Can you please tell me what I've done wrong. Thanks!
if score[0] >= 10 {
pauseGame()
let textLabel = SKLabelNode(fontNamed: "Helvetica")
textLabel.fontSize = 30
textLabel.fontColor = SKColor.white
textLabel.position = CGPoint(x: 20, y: 20)
textLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center
textLabel.text = "HELLO"
addChild(textLabel)
reload()
}
else if
score [1] >= 10 {
pauseGame()
sleep(5)
let textLabel = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
textLabel.fontSize = 30
textLabel.fontColor = SKColor.white
textLabel.position = CGPoint(x: 20, y: 20)
textLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center
textLabel.text = "HELLO"
addChild(textLabel)
reload()
}

Just change
reload()
to
reload(gestureRecognizer: nil)
and reload method parameter should be UITapGestureRecognizer? optional.
func reload(gestureRecognizer: UITapGestureRecognizer?) {
let skView = self.view!
skView.presentScene(scene)
}
Another short way is
change action method that declared with UITapGestureRecognizer
...action: #selector(self.reload())...
means remove (_:) no need to do anythings more just call reload() function.

Related

error "Value of type 'UIViewController' has no member..." when moving a func inside extension

I need to move a method for adding and removing a logging view inside an Extension, in order to give it to every controller. to do so I added a inout UIVew parameter to original method, where I used a global var for the view. no I have this error
Value of type 'UIViewController' has no member 'containerForLoading'
removing self from self.containerForLoading will give error:
Escaping closure captures 'inout' parameter 'containerForLoading'
inside the animate closure (see the comment)
is all wrong the entire process or I am lost at the last step?
extension UIViewController {
func showLoadingView(containerForLoading: inout UIView, uponView: UIView) {
containerForLoading = UIView(frame: uponView.bounds)
uponView.addSubview(containerForLoading)
containerForLoading.backgroundColor = .white
containerForLoading.alpha = 0
UIView.animate(withDuration: 0.24) { self.containerForLoading.alpha = 0.8 } //here the error
let activivityIndicator = UIActivityIndicatorView()
containerForLoading.addSubview(activivityIndicator)
activivityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)
])
activivityIndicator.startAnimating()
}
func removeLoading(containerForLoading: inout UiView, uponView: UIView) {
containerForLoading.removeFromSuperview()
}
}
this is the code inside the original viewController
using this var
var containerForLoading = UIView()
called this way when needed
self.showLoadingView(uponView: self.view)
extension ViewController {
func showLoadingView(uponView: UIView) {
containerForLoading = UIView(frame: uponView.bounds)
uponView.addSubview(containerForLoading)
containerForLoading.backgroundColor = .white
containerForLoading.alpha = 0
UIView.animate(withDuration: 0.24) { self.containerForLoading.alpha = 0.8 }
let activivityIndicator = UIActivityIndicatorView()
containerForLoading.addSubview(activivityIndicator)
activivityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)
])
activivityIndicator.startAnimating()
}
func removeLoading(uponView: UIView) {
containerForLoading.removeFromSuperview()
}
}
You could make loadingContainerTag a local variable, and change the parameter name to something else. Then assign to the parameter just after you create the container view:
extension UIViewController {
func showLoadingView(containerForLoadingProperty: inout UIView, uponView: UIView) {
// local variable!
let containerForLoading = UIView(frame: uponView.bounds)
// also set property
containerForLoadingProperty = containerForLoading
uponView.addSubview(containerForLoading)
containerForLoading.backgroundColor = .white
containerForLoading.alpha = 0
// no "self."!
UIView.animate(withDuration: 0.24) { containerForLoading.alpha = 0.8 }
// ... everything else is the same
removeLoading could just have one non-inout parameter:
func removeLoading(containerForLoadingProperty: UIView) {
containerForLoadingProperty.removeFromSuperview()
}
But...
It is very weird for a method that shows a loading indicator, to need an inout parameter. It shouldn't assign to a special property that the caller provides. It should just show the loading indicator!
The purpose of your containerForLoading property is so that in removeLoading, you know which view to remove. If you don't store the containerForLoading view somewhere in a property, you wouldn't know which view to remove, right? Well, we can use the tag property of a view to identify views, so you can just make containerForLoading a local variable, and later in removeLoading, use its tag to find it.
extension UIViewController {
static let loadingContainerTag = <a number you like>
func showLoadingView(uponView: UIView) {
// local variable!
let containerForLoading = UIView(frame: uponView.bounds)
uponView.addSubview(containerForLoading)
containerForLoading.backgroundColor = .white
containerForLoading.alpha = 0
// set tag
containerForLoading.tag = UIViewController.loadingContainerTag
// no "self."!
UIView.animate(withDuration: 0.24) { containerForLoading.alpha = 0.8 }
let activivityIndicator = UIActivityIndicatorView()
containerForLoading.addSubview(activivityIndicator)
activivityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)
])
activivityIndicator.startAnimating()
}
func removeLoading(uponView: UIView) {
// find view with the tag
uponView.viewWithTag(UIViewController.loadingContainerTag)?.removeFromSuperview()
}
}

Finish action running on a SpriteNode without disabling touches

i have a player who's physicsBody is divided to parts (torso, legs, head, ect), now i have a button on screen that control the movement of the player, if the button is pressed to the right the player moves to the right, and that part works fine. the problem is every time the movement changes the walk() method is called and what it does is animate the player legs to look like its walking, but if the movement doesn't stop then is keeps calling the walk() without it finishing the animation, so it looks like its stuck back and forth. what i am trying to achieve is for the player to be able to walk constantly but for the walk() (animation method) to be called once, finish, then called again(as long as the button to walk is still pressed). here what i have so far:
func walk(){
let leftFootWalk = SKAction.run {
let action = SKAction.moveBy(x: 1, y: 0.1, duration: 0.1)
let actionReversed = action.reversed()
let seq = SKAction.sequence([action, actionReversed])
self.leftUpperLeg.run(seq)
self.leftLowerLeg.run(seq)
}
let rightFootWalk = SKAction.run {
let action = SKAction.moveBy(x: 0.4, y: 0.1, duration: 0.1)
let actionReversed = action.reversed()
let seq = SKAction.sequence([action, actionReversed])
self.rightUpperLeg.run(seq)
self.rightLowerLeg.run(seq)
}
let group = SKAction.sequence([leftFootWalk, SKAction.wait(forDuration: 0.2), rightFootWalk])
run(group)
}
extension GameScene: InputControlProtocol {
func directionChangedWithMagnitude(position: CGPoint) {
if isPaused {
return
}
if let fighter = self.childNode(withName: "fighter") as? SKSpriteNode, let fighterPhysicsBody = fighter.physicsBody {
fighterPhysicsBody.velocity.dx = position.x * CGFloat(300)
walk()
if position.y > 0{
fighterPhysicsBody.applyImpulse(CGVector(dx: position.x * CGFloat(1200),dy: position.y * CGFloat(1200)))
}else{
print("DUCK") //TODO: duck animation
}
}
}
First of all you can use SKAction.runBlock({ self.doSomething() })
as the last action in your actions sequence instead of using completion.
About your question, you can use hasAction to determine if your SKNode is currently running any action, and you can use the key mechanism for better actions management.
See the next Q&A
checking if an SKNode is running a SKAction
I was able to solve this, however i'm still sure there is a better approach out there so if you know it be sure to tell. here is what i did:
first i added a Boolean called isWalking to know if the walking animation is on(true) or off(false) and i set it to false. then i added the if statement to call walk() only if the animation is off(false) and it sets the boolean to on(true).
func directionChangedWithMagnitude(position: CGPoint) {
if isPaused {
return
}
if let fighter = self.childNode(withName: "fighter") as? SKSpriteNode, let fighterPhysicsBody = fighter.physicsBody {
fighterPhysicsBody.velocity.dx = position.x * CGFloat(300)
print(fighterPhysicsBody.velocity.dx)
if !isWalking{
isWalking = true
walk()
}
}
}
and i changed walk() to use completions blocks and set the Boolean to off(false) again
func walk(){
let walkAction = SKAction.moveBy(x: 5, y: 5, duration: 0.05)
let walk = SKAction.sequence([walkAction, walkAction.reversed()])
leftUpperLeg.run(walk) {
self.rightUpperLeg.run(walk)
}
leftLowerLeg.run(walk) {
self.rightLowerLeg.run(walk, completion: {
self.isWalking = false
})
}
}

Unable to click on Textfield on GMaps for iOS Subview

I would like to add a searchbar (like in the Google Maps App for iOS).
So to look like the same as in that App, ill created a UITextField.
For example:
override func viewDidLoad() {
/* Add Google Map */
let camera = GMSCameraPosition.cameraWithLatitude(49.077872,longitude: 19.450339, zoom: 17)
mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
self.view = mapView
mapView.delegate = self
mapView.indoorDisplay.delegate = self
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.padding = UIEdgeInsetsMake(64, 0, 64, 0)
mapView.setMinZoom(15, maxZoom: 19)
// add Searchbar
let screenWidth = UIScreen.mainScreen().bounds.width
searchTextField = UITextField(frame: CGRectMake(16, 50, screenWidth - 32, 40))
searchTextField.delegate = self
searchTextField.placeholder = "Gebäude und Räume suchen"
searchTextField.backgroundColor = UIColor.whiteColor()
searchTextField.clearButtonMode = UITextFieldViewMode.WhileEditing
searchTextField.layer.borderWidth = 1
searchTextField.layer.borderColor = UIColor.customGrey().CGColor
searchTextField.font = UIFont.systemFontOfSize(16.0)
let showCategories = UIButton(type: .Custom)
showCategories.frame = CGRectMake(0, 0, 40, 40)
showCategories.setTitle(String.fontAwesomeIconWithCode("fa-calendar-plus-o"), forState: .Normal)
showCategories.setTitleColor(UIColor.customDarkGrey(), forState: .Normal)
showCategories.titleLabel?.font = UIFont.fontAwesomeOfSize(20)
showCategories.addTarget(self,action: "calendarAddButtonPressed",forControlEvents: UIControlEvents.TouchUpInside)
searchTextField.rightView = showCategories
searchTextField.rightViewMode = .Always
searchTextField.userInteractionEnabled = true
self.mapView.addSubview(searchTextField)
The Button works fine, but i am unable to focus on the TextField.
When ill set (in ViewDidLoad).
searchTextField.becomesFirstResponder()
The Keyboard is there.
Also that event:
func textFieldDidBeginEditing(textField: UITextField) {
print("start searching")
self.becomeFirstResponder()
}
Is not fireing.
Any ideas? When the SubView is not on the top level - the button should not work too, or?
The accepted answer didn't help me at all, but it turns out there's another question on SO that is similar, but with Objective C.
All GMSMapViews have a GMSBlockingGestureRecognizer by default, and that is why your textField isn't working the way you want it to. The solution is to remove it, so you can put this in your viewDidLoad() method:
Swift 3
for gesture in mapView.gestureRecognizers! {
mapView.removeGestureRecognizer(gesture)
}
Alternatively, if you for some reason added other gesture recognizers to the mapView, you can do this:
for (index,gesture) in mapView.gestureRecognizers!.enumerated() {
if index == 0 {
mapView.removeGestureRecognizer(gesture)
}
}
Since the GMSBlockingGestureRecognizer is added by default, it will always be the first one in the array of gesture recognizers.
Also, make sure you put the for loop somewhere it will only be called once because if it is called multiple times, you could remove other gestures unintentionally.
Ok ill found a solution for someone who is struggling with the same problems.
When you set:
self.view = mapView
It looks like that you are unable to set ANY subviews, because that Views never receive touch events.
So in that case use:
mapView = GMSMapView.mapWithFrame(self.view.bounds, camera: camera)
self.view.addSubview(mapView)
Then all SubViews work fine.

How do you setup a loop with an interval/delay?

I'm trying to make a Wack-a-Mole game. The way I am doing so is by having one button randomly appear and disappear around the screen after someone has tapped it. If they do not tap the button within one second after it reappears, then it will disappear and find a new position, then reappear and wait one second and repeat the above steps. However, whenever I run this code, it doesn't do that. It moves positions only if I get rid of the 'while' statement and the 'if else' statement. Why won't it loop, disappear, reappear, etc?
Delay is this: https://stackoverflow.com/a/24318861/5799228
#IBAction func moveButton(button: UIButton) {
while self.WaffleButton.hidden == true || false {
if self.WaffleButton.hidden == false {
self.WaffleButton.hidden = true
delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
} else { delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
}
}
}
You are using while in a way it is not meant to be used.
This is how you should use while :
var someCondition = true
while someCondition {
// this will loop as fast as possible untill someConditionIsTrue is no longer true
// inside the while statement you will do stuff x number of times
// then when ready you set someCondition to false
someCondition = false // stop
}
This is how you are using while :
let someConditionThatIsAlwaysTrue = true
while someConditionThatIsAlwaysTrue {
// condition is always true, so inifinite loop...
// this creates a function that is executed 3 seconds after the current looping pass of the while loop.
// while does not wait for it to be finished.
// while just keeps going.
// a fraction of a second later it will create another function that will execute 3 seconds later.
// so after 3 seconds an infite amount of functions will execute with a fraction of a second between them.
// except they won't, since the main thread is still busy with your infinite while loop.
delay(3) {
// stuff
}
}
How to do it the right way :
don't ever use while or repeat to "plan" delayed code execution.
Split up the problem in smaller problems:
Issue 1 : Creating a loop
A loop is created by have two functions that trigger each other.
I will call them execute and executeAgain.
So execute triggers executeAgain and executeAgain triggers execute and then it starts all over again -> Loop!
Instead of calling execute and executeAgain directly, you also create a start function. This is not needed but it is a good place to setup conditions for your looping function. start will call execute and start the loop.
To stop the loop you create a stop function that changes some condition.
execute and executeAgain will check for this condition and only keep on looping if the check is successful. stop makes this check fail.
var mustLoop : Bool = false
func startLoop() {
mustLoop = true
execute()
}
func execute() {
if mustLoop {
executeAgain()
}
}
func executeAgain() {
if mustLoop {
execute()
}
}
func stop() {
mustLoop = false
}
Issue 2: Delayed Execution
If you need a delay inside a subclass of NSObject the most obvious choice is NSTimer. Most UI classes (like UIButton and UIViewController) are subclasses of NSObject.
NSTimer can also be set to repeat. This would also create a loop that executes every x seconds. But since you actually have 2 alternating actions it makes more sense to adopt the more verbose looping pattern.
An NSTimer executes a function (passed as Selector("nameOfFunction")) after x amount of time.
var timer : NSTimer?
func planSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
}
func doSomething() {
// stuff
}
If you need a delay in another class/struct (or you don't like NSTimer) you can use the delay function that matt posted.
It will execute whatever you enter in the closure after x amount of time.
func planSomething() {
delay(3) {
doSomething()
}
}
func doSomething() {
// stuff
}
Combining the two solutions:
By using the loop pattern above you now have distinct functions. Instead of calling them directly to keep the loop going. You insert the delay method of your choice and pass the next function to it.
So NSTimer will have a Selector pointing to execute or executeAgain and with delay you place them in the closure
How to implement it elegantly:
I would subclass UIButton to implement all this. Then you can keep your UIViewController a lot cleaner. Just choose the subclass in IB and connect the IBOutlet as usual.
This subclass has a timer attribute that will replace your delay.
The button action wacked() is also set in the init method.
From your UIViewController you call the start() func of the button. This will start the timer.
The timer will trigger appear() or disappear.
wacked() will stop the timer and make the button hide.
class WackingButton : UIButton {
var timer : NSTimer?
var hiddenTime : NSTimeInterval = 3
var popUpTime : NSTimeInterval = 1
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
func start() {
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func appear() {
self.center = randomPosition()
self.hidden = false
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(popUpTime, target: self, selector: Selector("dissappear"), userInfo: nil, repeats: false)
}
func dissappear() {
self.hidden = true
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func wacked() {
self.hidden = true
timer?.invalidate()
}
func randomPosition() -> CGPoint {
// Find the width and height of the enclosing view
let viewWidth = self.superview?.bounds.width ?? 0 // not really correct, but only fails when there is no superview and then it doesn't matter anyway. Won't crash...
let viewHeight = self.superview?.bounds.height ?? 0
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - frame.width
let yheight = viewHeight - frame.height
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
let x = xoffset + frame.width / 2
let y = yoffset + frame.height / 2
return CGPoint(x: x, y: y)
}
}
Your UIViewController :
class ViewController: UIViewController {
#IBOutlet weak var button1: WackingButton!
override func viewDidAppear(animated: Bool) {
button1.start()
}
}

Swift UIPageViewController & UIActivityIndicatorView

Is anyone aware of an issue with stopping a UIActivityIndicatorView (AI) / removing a UIView that has been added to a UIPageViewController?
I am showing the AI using:
override func viewWillLayoutSubviews() {
actInd.frame = CGRectMake(0,0, 80, 80)
actInd.center.x = self.view.center.x
actInd.center.y = self.view.center.y - 40
actInd.hidesWhenStopped = true
actInd.layer.cornerRadius = 5
actInd.alpha = 0.5
actInd.backgroundColor = UIColor.blackColor()
actInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
self.view.addSubview(actInd)
actInd.startAnimating()
// This next line prints out the details of each subview on the page for testing purposes
listSubviews(self.view)
I am also, for testing purposes, adding a blank UIView to the view using:
aView.frame = CGRectMake(0, 0, 200, 200)
aView.backgroundColor = UIColor.blueColor()
self.view.addSubview(aView)
}
Then I send my Parse.com query using a delegate to advise the UIPageViewController (e.g. this view) when the data is ready. This is working fine and I have tested that the data returns correctly, and that the expected method is being called.
In this method I try to stop the UIActivityViewController & try to hide aView:
self.actInd.stopAnimating()
aView.removeFromSuperView()
This didn't work so I tried:
self.actInd.removeFromSuperView()
This didn't work either. So I then tried to search through the subviews on the current view using:
func populateBoards(boards: [Board]) {
println(self.actInd) // PRINT LINE 1
var subviews = self.view.subviews
for v in subviews {
if v.isKindOfClass(UIActivityIndicatorView) {
println("YES, it is an activity view indicator \(v)") // PRINT LINE 2
v.stopAnimating()
v.removeFromSuperview()
}
}
self.boards = boards
println("Populated Boards!") // PRINT LINE 3
}
PRINT LINE 1 outputs: >
PRINT LINE 2 outputs: YES, it is an activity view indicator >
PRINT LINE 3 outputs: "Populated boards!"
EDIT:
The UIPageViewController is setup with the following code (in case it helps):
func setupUIPageView() {
self.pageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
self.pageViewController.delegate = self
self.pageViewController.dataSource = self
var startVC = self.viewControllerAtIndex(0) as ViewController
var viewControllers:[ViewController] = [startVC]
self.pageViewController.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
self.pageViewController.view.alpha = 0.0
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
Help! Thanks all in advance :D
You should call all your UI related code on the main queue. Your Parse query is async and the completion block might be called on a different thread.
Wrap this call in this block:
NSOperationQueue.mainQueue().addOperationWithBlock {
self.actInd.stopAnimating()
}

Resources