SceneKit Button show delay - ios

I'm trying to create a 3D game with Swift. When the player dies, a UIButton should show on the screen. The problem is that the button has a very big delay (something like 5 seconds) till the button shows. I've used the same code as I would use for a SpriteKit game. I tried to print a message in the console when the button should show and this message came without delay.
My Button:
func createRespawnButton() {
restartButton = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width / 3, height: 50))
restartButton.setTitle("Play Again!", for: UIControlState.normal)
restartButton.setTitleColor(UIColor.cyan, for: UIControlState.normal)
restartButton.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 2)
restartButton.addTarget(self, action: #selector(self.restartGame), for: UIControlEvents.touchDown)
self.view.addSubview(restartButton)
}
This function is called when 2 cars collide with each other. Can someone help me to make my button show on the screen without such a big delay?
Thanks!

From the documentation:
For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.
Is your function called in the main thread? SCNSceneRendererDelegate delegation methods are called in SceneKit's rendering queue. If that's where you detect collisions, you will want to dispatch your function call to the main queue:
DispatchQueue.main.async {
createRespawnButton()
}

Related

Changing frames of multiple UIView dependent on each other doesn't work efficiently in dispatch main queue

I have to change multiple UI's as imageView (Background for Label) is dependent on label (as text changes dynamically it increases dynamically). And avatar is always below label_bg_img.
So I have realised that frames of label change and hence I assign the same frame of label to imageView taking into account the padding required too.
Also, I have a complicated layout with many element in it. And there are lot of UI component updated simultaneously.
And here is the code. Which cause a lot of randomness:
func changeFrame(){
DispatchQueue.main.async{
self.label_bg_img.frame = CGRect(x: self.label_bg_img.frame.minX, y: 100, width: self.label_txt.frame.width + 15, height: self.label_txt.frame.height + self.label_txt.frame.height/3)
self.label_txt.center = self.label_bg_img.center
self.label_txt.center.y = self.label_bg_img.center.y - 5
self.avatar.frame = CGRect(x: self.label_bg_img.frame.maxX - self.avatar.frame.width, y: self.label_bg_img.frame.maxY, width: self.avatar.frame.width, height: self.avatar.frame.height)
}
}
So frames do change sometimes but sometimes they don't. I have also created a button which calls the function changeFrame. So If I click on this button in some 4 - 5 times the frames are into correct form as I want them to be.
I have realized that I am missing something with frame update. or the synchronization in DispatchQueue.main.async.
Thanks in advance.

How to programmaticaly add button in iOS game app swift 3?

Can someone explain the code for a programatically added button in an iOS game application for swift 3 Xcode 8? All the other threads on this topic we're in single view and didn't work for me. I couldn't figure out how to add buttons to the game app Main.storyboard, so I'm trying to make a programattically added button. This is the code I'm trying to use now but doesn't work:
var playAgain = UIButton()
playAgain.setTitle("Play Again", for: .normal)
playAgain.setTitleColor(UIColor.black, for: .normal)
playAgain.backgroundColor = SKColor.green
playAgain.layer.borderWidth = 2
playAgain.layer.cornerRadius = 18
playAgain.frame = CGRect(x: (self.frame.width)/2, y: (self.frame.height)/2, width: 100, height: 36)
self.view?.addSubview(playAgain)
Why would the buttons in single view be different in game apps? Also, when(and if) this is created, how would I modify the Touches ended method to know when the button was touched?
Your code is adding the button to the .view, but using the coordinate system of the SKScene. So, your button is there, just not in view.
Assuming you want the button to be centered on the screen (at least, for now), change the placement to:
playAgain.frame = CGRect(x: 0, y: 0, width: 100, height: 36)
playAgain.center = (self.view?.center)!
self.view?.addSubview(playAgain)
This will put the button above the game scene (z-layer, that is), so you can use normal button tap without needing to deal with touches. So, right after you add the button:
self.view?.addSubview(playAgain)
playAgain.addTarget(self, action: #selector(playAgainTapped(_:)), for: .touchUpInside)
and then elsewhere in your class:
func playAgainTapped(_ sender: Any?) -> Void {
print("Play again was Tapped!")
// take whatever action you want here
}
There is another way to create a button programmatically. You can create an empty UIView and override touch method. Also you are able to process touch event on this view and simulate buttons action. I think this is a fastest way for you.

Error when trying to click in a view created programmatically swift 3

I created a view programmatically, like a popup coming from the top with some text and images in there!
alert = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width , height: 0))
alert?.frame.origin.y = (textLable?.frame.height)!
alert?.frame.size.height = (textLable?.frame.height)!
alert?.backgroundColor = self.arrayOptions.colorBackground
and then I'm trying to add a UITapGestureRecognizer to that view like this inside a setup func that is called in the init.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.teste))
tap.numberOfTapsRequired = 1
alert?.addGestureRecognizer(tap)
im adding that view like this in a UitableViewController:
self.popView = PopupView(frame: CGRect(x: 0 , y: 0 , width:self.view.frame.width, height: 0), with: PopUpOptions.error, originY: 0,description:"blablabla")
self.view.addSubview(self.popView!)
But when I tap on the view nothing happens, but when I tap repeatedly over and over this error occurs:
<_UISystemGestureGateGestureRecognizer: 0x174186f50>: Gesture: Failed
to receive system gesture state notification before next touch
But I cant seem to find an answer for this, could anyone help me pls!
Thank you!
here is the GitHub link https://github.com/Coimbraa/AlertsPopup_framework for my framework
Took a look at your GitHub repo - Couple notes...
I see a lot of "!" in your code. Each one of those is a potential crash. For example, I had to make a number of edits just to get it to run at all (I don't have the image assets the code is expecting).
In PopUpView.swift change the init() func to this (just added the clipsToBounds line):
override public init(frame: CGRect) {
super.init(frame: frame)
setup()
self.clipsToBounds = true
}
Now, when you call popView?.toggleStatus() you probably won't see anything.
Your PopupView "container" has a height of Zero. Without clipping its contents, you see the content, but you can't interact with it.
I changed your animation block in toggleStatus() to this:
UIView.animate(withDuration: 0.5, animations: {
if let sz = self.alert?.frame.size {
self.frame.size.height = sz.height
}
self.alert?.frame.origin.y = (self.startY)! + (self.status ? 0 : -((self.textLable?.frame.height)! + self.startY))
}) { (finished:Bool) in
// ...
and I could now tap into and edit the TextView, and tap elsewhere and get print("pressed") output to the debug console.
I did not dig further, so I don't know if/where you need to put additional code to reset the frame-height back to Zero (or maybe it gets hidden or removed, whatever).
Try this:
alert.isUserInteractionEnabled = true
Set this property to true for those views you want to tap on (where you add the tap gesture recognizer).
You are adding that Custom Alert View in Table View Controller.
First try what Tung Fam has suggested, if it doesn't work then,
Trying adding it on window like:
self.view.window.addSubview(self.popView!)
Also set the frame of the window to the popupView.
As I'm seeing you are setting target for TapGesture as self in
let tap = UITapGestureRecognizer(target: self, action: #selector(self.teste))
Here self is object of UIView (your alert view).
So according to this line your tapGesture listener must be implemented in alert? class. And you need to use custom protocol or custom delegate to get the action of tapGesture in other classes.

ProgressView not showing on View Controller using Swift

I am showing an alertController and if the user clicks Yes an ProgressView should be shown, but unfortunately the Progressview and the label does not appear. How can I refresh my ViewController. Here the cod that is executed for the yes-handler of the alertController. The code will be executed without problem, but the progressview is not appearing:
func initProgressView(){
turn = 0
let xCoord = self.view.center.x
let yCoord = self.view.center.y + 10
progressLabel = UILabel(frame: CGRect(x: xCoord, y: yCoord, width: 100, height: 23))
progressLabel.text = "0 %"
progressLabel.font = UIFont.boldSystemFontOfSize(14)
progressView.center = self.view.center
progressView.trackTintColor = UIColor.lightGrayColor()
progressView.tintColor = UIColor.blueColor()
// self.view.backgroundColor = UIColor.yellowColor()
self.view.addSubview(progressLabel)
self.view.addSubview(progressView)
}
here the complete sequence of call:
initProgressView() //see previous post
then call of importData:
func importData (source : ImportDataInterface, data : NSData, progressStep : Int) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
source.importData(data)
dispatch_async(dispatch_get_main_queue(), {
self.counter += progressStep
return
})
})
}
and finally in the calling function:
progressView.removeFromSuperview()
progressLabel.removeFromSuperview()
How can I refresh the ViewController or what else could be the reason why the progressView does not appear. Can it be, that constraints or autolayout issues are the problem.
thanks
Arnold
You haven't provided enough information. You need to show us the sequence of calls.
iOS doesn't render changes to the screen until your code returns. The following coding pattern will not work:
create progress indicator
add progress indicator to superview
start progress indicator spinning
do long-running task
stop progress indicator
Instead, what you have to do is something like this:
create progress indicator
add progress indicator to superview
start progress indicator spinning
invoke long-running task with a call to dispatch_after and a delay of 0:
dispatch_after(main queue, delay 0)
{
do long running task
stop activity indicator
}
The call to dispatch_after queues up your closure to run on the main thread the next time your code returns and the event loop even if the delay value is 0.
looks like that progressView overlaps progressLabel, probably you need change last 2 strings order?
self.view.addSubview(progressView)
self.view.addSubview(progressLabel)

Swift / iOS - UIButton Sender with more information

Lets say you have a calculator you're building for iOS in Swift. When a user taps an operation (division, addition, subtraction, etc) I'd like to avoid having a separate method to handle each.
Instead, i'd prefer to have a factory which, based on the sender, determines the correct OperationType sub-class to use (Subtraction, Multiplication, etc)
My question is: is it proper practice to store information directly on the UI Button in Xcode? For instance, on the Addition button (+), if I tagged it with Addition, I could then use that information to load the Addition class.
Similarly, for each digit, i would prefer to have one function which handles a user pushing any digit. However, to determine the sender, it seems somewhat sloppy to use sender.title, since it binds the visual title of the digit, to the code. Is it then appropriate to provide tags to deal with this, or is there another system of handling this?
You can certainly do that, and sometimes it makes for a clean design. View objects have a tag property (an Int.) You can put a switch statement on your action method(s) that switches on the tag value. Generally you should use tag values starting from 1 or greater, since 0 is the default tag value.
If you need more than an integer value you can either save your values into an array that you index into with the tag value or you can use associative storage to actually store objects attached to your buttons. Do a search on "Cocoa associated objects" for more information on that technique. Or you can take a look at a demo project I put on github: https://github.com/DuncanMC/AssocitiveStorageDemo (Written in Objective-C, but the technique is the same in Swift)
override func viewDidLoad() {
super.viewDidLoad()
let button1 = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 200));
let button2 = UIButton(frame: CGRect(x: 0, y: 250, width: 200, height: 200));
button1.backgroundColor = UIColor.greenColor();
button2.backgroundColor = UIColor.yellowColor();
button1.addTarget(self, action: "buttonTaped:", forControlEvents: .TouchUpInside);
button2.addTarget(self, action: "buttonTaped:", forControlEvents: .TouchUpInside);
button1.tag = 0;
button2.tag = 1;
self.view.addSubview(button1);
self.view.addSubview(button2);
}
func buttonTaped(button:UIButton){
switch(button.tag){
case 0:
print("button 1 taped");
break;
case 1:
print("button 2 taped");
break;
default:
break;
}
}
You can use the tag property
func buttonClicked(sender:AnyObject) {
let button = sender as! UIButton
if button.tag == 0 {
...
}
else if button.tag == 1 {
...
}
}

Resources