Programatically added UIButton doesn't go away after switching scenes - swift

I'm using a game app template in Swift 3 and when I transition from my 'start' screen to my 'Game' scene, the button from the 'start' screen doesn't go away. I read other people posts similar to this but nothing helped. My button is a programatically added uibutton with a uibezierpath rounded rectangle behind the button to make it look nice. The problem is, it(the button, and the UIBezierpath) won't go away when I change scenes - it's in the exact same spot as the 'start' screen.
My Button code with the UIBezierpath:
let playAgain = UIButton()
playAgain.frame = CGRect(x: 225, y: 247, width: 115, height: 36)
playAgain.backgroundColor = SKColor.lightGray
playAgain.setTitle("Play", for: .normal)
playAgain.setTitleColor(.black, for: .normal)
self.view?.addSubview(playAgain)
playAgain.addTarget(self, action: #selector(playAgainTapped(_:)), for: .touchUpInside)
//now for the bezierpath/ rounded rect
//let doYourPath = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 100, height: 36))
//this also works
let roundRect = UIBezierPath(roundedRect: CGRect(x: 218, y: 240, width: 130, height: 50), cornerRadius: 18)
let layer = CAShapeLayer()
layer.path = roundRect.cgPath
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.lightGray.cgColor
self.view?.layer.addSublayer(layer)
func playAgainTapped(_ sender: Any?) -> Void {
print("***********")
backToGame()
}
Switch scenes code:
func backToGame(){
removeAllChildren()
run(SKAction.sequence([
SKAction.wait(forDuration: 3.0),
SKAction.run() {
// 5
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let scene = GameScene(size: self.size)
self.view?.presentScene(scene, transition:reveal)
}
]))
}
Any ideas?

You are presenting the scene on the same view as the button's superview.
Since the scene is independent from the views that are on the scene, your button will remain untouched, so you should explicitly remove the button if you want it to be removed.
Declare the button and the rounded rectangle globally and remove them from their superview/superlayer in backToGame.
let playAgain = UIButton()
let layer = CAShapeLayer()
func backToGame(){
removeAllChildren()
playAgain.removeFromSuperview()
layer.removeFromSuperlayer()
run(SKAction.sequence([
SKAction.wait(forDuration: 3.0),
SKAction.run() {
// 5
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let scene = GameScene(size: self.size)
self.view?.presentScene(scene, transition:reveal)
}
]))
}

Related

Why does the selector, #objc, function add a subview, of my button image (as a UIImageView), in my UIButton?

I first declare the button
let balloon = UIButton()
A background image then gets added to the balloon
balloon.setBackgroundImage(UIImage(named:"balloon.jpg"), for: .normal)
An image view of the points get added to the balloon as a subview
subView = UIImageView(image: UIImage(named: "1") )
subView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
subView.contentMode = UIView.ContentMode.scaleAspectFit
subView.center = CGPoint(x: balloon.frame.width/2, y: balloon.frame.height/2)
balloon.addSubview(subView)
I then use the addTarget function for the balloon
balloon.addTarget(self, action: #selector(pop), for: .touchUpInside)
After pop gets called (when the user taps on the balloon), the balloon now contains 2 subviews
- At index 0 of balloon.subviews, there is a UIImageView that is essentially the picture of the balloon with the same dimensions as the balloon button
- And the subs view that I added (aka the points)
here is how I found this problem in my addTarget function (pop):
#objc func pop(_ balloon: UIButton){
print("4.This is the balloon after calling pop \(balloon)")
print("5. This is the subview of the balloon after calling pop \(balloon.subviews)")
Ive added print statements in my function that verifies that the balloons are the same in both the pop func and my balloon creation func
I have already looked at the documentation for both UIButton and addTarget and neither of them have specified why the background image of the button gets created as a subview of the button when the selector func gets called
There shouldn't be that extra UIImageView in my UIButton since I never added that
What you're seeing has nothing to do with your selector / #objc func...
Many UIKit classes do a lot of "under-the-hood" work.
In the case of UIButton, subviews are only added as-needed.
For example, if this is all the code you execute:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
The resulting button has Zero subviews.
If we do this:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
The resulting button now has 1 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(img, for: .normal)
The resulting button now has 2 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
The resulting button now has 3 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
balloon.setImage(imgB, for: [])
And now we have 4 subviews.
Apple strongly discourages messing with the internals of UIButton. You might be better off creating a view subclass that contains a button and any additional subviews, rather than your current approach.
Worth Noting
the subviews may not be added until they are needed. So, if you set the title or image or background image in viewDidLoad(), those subviews will not be created until viewDidLayoutSubviews ... or even until the button is actually rendered.
the title label may be created even when you don't expect it to. For example, if the only thing you do to the button is constrain its position, it will get a title label. However, if you set the background image, the title label will be omitted.
Here is a quick example to demonstrate some of the resulting subview counts:
class BtnVC: UIViewController {
var buttons: [UIButton] = []
let stack = UIStackView()
let infoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
return v
}()
var didLoadStr: String = ""
var didLayoutStr: String = ""
var didAppearStr: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let largeFont = UIFont.systemFont(ofSize: 32)
let configuration = UIImage.SymbolConfiguration(font: largeFont) // <1>
guard let img2 = UIImage(systemName: "02.circle.fill", withConfiguration: configuration),
let img3 = UIImage(systemName: "03.circle.fill"),
let img4 = UIImage(systemName: "04.circle.fill")
else {
return
}
var b: UIButton!
b = UIButton()
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
let v = UIImageView(image: img4)
v.tintColor = .systemRed
v.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
b.addSubview(v)
buttons.append(b)
stack.axis = .vertical
stack.spacing = 8
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 120.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
// add this first button *without* auto-layout
buttons[0].frame = CGRect(x: 80, y: 40, width: 200, height: 50)
view.addSubview(buttons[0])
// add the rest of the buttons to the stack view
buttons.forEach { v in
v.backgroundColor = .systemYellow
v.setTitleColor(.black, for: [])
if v != buttons.first {
stack.addArrangedSubview(v)
}
}
stack.addArrangedSubview(infoLabel)
var s = "didLoad : "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLoadStr += s + "\n"
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// first button is not using auto-layout constraints, so
// let's size it to match the buttons in the stack view
// and posiiton it 8-pts above the stack view
var r = buttons[1].bounds
r.origin.y = stack.frame.origin.y - r.height - 8.0
r.origin.x = stack.frame.origin.x
buttons[0].frame = r
var s = "didLayout: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLayoutStr += s + "\n"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var s = "didAppear: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didAppearStr += s + "\n"
infoLabel.text = didLoadStr + didLayoutStr + didAppearStr
}
}
That code will create 5 buttons, with each successive button getting another subview - title, image, backgroundImage, addSubview - and the "Info Label" at the bottom will show the subviews count at each stage (as is often the case, didLayoutSubviews() is called more than once):

Add drop shadow to xib view in Swift

I have added a card view from a Xib file which when tapped it slides from bottom-up to display the view.
I have am trying to customised the view and a drop shadow to the view which is not working.
The cornerRadius works fine otherwise.
func setupCard() {
menuCardVC = MenuCardVC(nibName:"MenuCardVC", bundle:nil)
menuCardVC.view.layer.shadowOpacity = 0.50
menuCardVC.view.layer.shadowRadius = 12
menuCardVC.view.layer.shadowColor = UIColor.black.cgColor
menuCardVC.view.layer.shadowOffset = CGSize.zero
menuCardVC.view.layer.cornerRadius = 25
self.addChild(menuCardVC)
self.view.addSubview(menuCardVC.view)
menuCardVC.view.frame = CGRect(x: 0, y: self.view.frame.height - cardHandleAreaHeight, width: self.view.bounds.width, height: cardHeight)
menuCardVC.view.clipsToBounds = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(VenueDetailsVC.handleCardTap(recognzier:)))
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(VenueDetailsVC.handleCardPan(recognizer:)))
menuCardVC.handleArea.addGestureRecognizer(tapGestureRecognizer)
menuCardVC.handleArea.addGestureRecognizer(panGestureRecognizer)
}//end setupCard
Remove the below line of code,
menuCardVC.view.clipsToBounds = true
Since your view hierarchy is simple you could use the following technique. Also, you don't need to use clipsToBounds.
let superview = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
superview.backgroundColor = .white
let subview = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
subview.backgroundColor = .red
// First provide the corner radius.
subview.layer.cornerRadius = 8
// Then provide the shadow parameters.
subview.layer.shadowOpacity = 1
// ...
// Do not clip to bounds since it will clip the shadow.
// ...
superview.addSubview(subview)

UIButton Not Working After Updated to Xcode 8.3

After I updated to Xcode 8.3 the following code is not working in Xcode Playgrounds. Before on Xcode 8.2.1, I was able to click on the button and the action would follow, but now nothing is happening when I click the button.
import UIKit
import SpriteKit
import PlaygroundSupport
let view = SKView(frame: CGRect(x: 0, y: 0, width: 550, height: 575))
let scene = SKScene(size: CGSize(width: 550, height: 575))
scene.backgroundColor = UIColor.white
scene.scaleMode = SKSceneScaleMode.aspectFit
view.presentScene(scene)
class Responder: NSObject {
#objc func nextScene() {
print("Next button pressed!")
}
}
let responder = Responder()
let nextButton = UIButton(type: .system)
nextButton.frame = CGRect(x: 250, y: 557.5, width: 40, height: 15)
nextButton.setTitle("Next", for: .normal)
nextButton.titleLabel?.font = UIFont(name: "Marker Felt", size: 17)
nextButton.tintColor = .black
nextButton.addTarget(responder, action: #selector(Responder.nextScene), for: .touchUpInside)
view.addSubview(nextButton)
PlaygroundPage.current.liveView = view
EDIT: I updated my code so that it includes PlaygroundPage.current.liveView = view and the code for setting up the view and scene.
You do not set a live view for the playground in your code. Normally, a playground will execute the code and stop - it does not wait for input or events etc, unless you explicitly specify it should.
You should add the following line at the end of the playground to specify that canvas is a live view - provided it is, I can't know since you have not provided the implementation for canvas.
PlaygroundPage.current.liveView = canvas

Menu system for Games in SpriteKit

I've looked at a few different post surrounding menu systems in SpriteKit and nothing really focusses on how to implement from start to finish (a basic play button starting the game, background and then sharing icons).
The reason I am looking is that I am a beginner and in the process of building my first basic game. I have used a few of these guides and none seem to give a proper walk through.
Thought it would be good to create a post for all beginners to start out. I have attempted some actual code but it hasn't been successful (quite a few errors though I can post if anyone is interested in seeing what NOT to do).
Anyway, here goes nothing😊 Thanks in advance all, lets see how we can get it done!
well, i believe this is a good question and setting up something as an answer here is not a bad idea. i've setup a starting menu with a title and three buttons in this very simple menu. that can be modified to the desired result later. first of all create a new file for starting menu by adding the file through project pane on the left. then create a spritekitScene also with the same name (startMenu or whatever you want to name it) then do the same for the settings and also credits or any other item you want to add to the start menu.
the next step is the change the entry point of the game from game scene to start menu in view controller swift. change the game scene to whatever you named your menu in this line if let scene = GameScene(fileNamed:"GameScene") to if let scene = StartMenu(fileNamed:"StartMenu") that will push the start menu rather than the game scene. go the start menu and add the following codes for creating a title and buttons to the view. i created the buttons and the text and added separate functions for each for the ease of following up, and then at the end you add the functions for the selector so when the buttons are pressed the new scene gets pushed. you can also add a return button to those scenes or do whatever else you want. so here is the complete start menu code:
import Foundation
import SpriteKit
class StartMenu : SKScene {
var btnPlay : UIButton!
var btnSettings : UIButton!
var btnCredits : UIButton!
var gameTitle : UILabel!
override func didMoveToView(view: SKView) {
// self.backgroundColor = UIColor.orangeColor()
setUpTitleText()
setupButtonPlay()
setupButtonSettings()
setupButtonCredits()
}
func setUpTitleText(){
gameTitle = UILabel(frame: CGRect(x: 0, y: 0, width: view!.frame.width, height: 300))
gameTitle.textColor = UIColor.blackColor()
gameTitle.font = UIFont(name: "Futura", size: 50)
gameTitle.textAlignment = NSTextAlignment.Center
gameTitle.text = "Game Title"
//gameTitle.backgroundColor = UIColor.whiteColor()
self.view?.addSubview(gameTitle)
}
func setupButtonPlay(){
btnPlay = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 100))
btnPlay.center = CGPoint(x: view!.frame.size.width / 2, y: 250)
btnPlay.titleLabel?.font = UIFont(name: "Futura", size: 25)
btnPlay.setTitle("Play", forState: UIControlState.Normal)
btnPlay.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
//btnPlay.backgroundColor = UIColor.grayColor()
btnPlay.addTarget(self, action: #selector(StartMenu.playTheGame), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(btnPlay)
}
func setupButtonSettings(){
btnSettings = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 100))
btnSettings.center = CGPoint(x: view!.frame.size.width / 2, y: 350)
btnSettings.titleLabel?.font = UIFont(name: "Futura", size: 25)
btnSettings.setTitle("Settings", forState: UIControlState.Normal)
btnSettings.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
//btnSettings.backgroundColor = UIColor.grayColor()
btnSettings.addTarget(self, action: #selector(StartMenu.pressTheSettings), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(btnSettings)
}
func setupButtonCredits(){
btnCredits = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 100))
btnCredits.center = CGPoint(x: view!.frame.size.width / 2, y: 450)
btnCredits.titleLabel?.font = UIFont(name: "Futura", size: 25)
btnCredits.setTitle("Credits", forState: UIControlState.Normal)
btnCredits.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
//btnCredits.backgroundColor = UIColor.grayColor()
btnCredits.addTarget(self, action: #selector(StartMenu.pressTheCredits), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(btnCredits)
}
func playTheGame(){
self.view?.presentScene(GameScene(), transition: SKTransition.crossFadeWithDuration(1.0))
btnPlay.removeFromSuperview()
gameTitle.removeFromSuperview()
btnCredits.removeFromSuperview()
btnSettings.removeFromSuperview()
if let scene = GameScene(fileNamed: "GameScene"){
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
func pressTheSettings(){
self.view?.presentScene(TheSettings(), transition: SKTransition.crossFadeWithDuration(1.0))
btnPlay.removeFromSuperview()
gameTitle.removeFromSuperview()
btnCredits.removeFromSuperview()
btnSettings.removeFromSuperview()
if let scene = TheSettings(fileNamed: "TheSettings"){
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
func pressTheCredits(){
self.view?.presentScene(TheCredits(), transition: SKTransition.crossFadeWithDuration(1.0))
btnPlay.removeFromSuperview()
gameTitle.removeFromSuperview()
btnCredits.removeFromSuperview()
btnSettings.removeFromSuperview()
if let scene = TheCredits(fileNamed: "TheCredits"){
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
}
in this scenario i have hard coded the titles and the buttons, but you can add your own images if you want. this is just a simple way of creating a menu but there are many other ways to do this. as you go along you can build your own in anyway you like.
good luck.

How to make tappable UIImages that run functions?(Swift)

(I just started using Swift a few days ago and am relatively new to programming, so please bear with me.) I am trying to make random blocks appear on the screen, and the user must tap them to make them disappear. I have been able to create the blocks, but I have no idea how to actually make them tappable. Can someone please help me? This is my code so far:
func createBlock(){
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.life-=1
})
}
I want to make the block disappear when tapped; any suggestions on how I can do that?
It's extremely simple to do, just use a UITapGestureRecognizer. I typically initialize the recognizer as a global variable.
let tapRecognizer = UITapGestureRecognizer()
Then in your viewDidLoad:
// Replace with your function
tapRecognizer.addTarget(self, action: "removeBlock")
imageView.addGestureRecognizer(tapRecognizer)
func removeImage(gesture: UIGestureRecognizer) {
gesture.view?.removeFromSuperview()
}
func createBlock() {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
imageView.userInteractionEnabled = true // IMPORTANT
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "removeImage:"))
self.view.addSubview(imageView)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, { _ in
imageView.removeFromSuperview()
self.life-=1
})
}
Notice imageView.userInteractionEnabled = true it is really important because that will allow touch events on that view, it is set to false as defaul in UIImgageView.