ui text field updating SKLabelNode - swift

I am working on a code to allow a user to update a SKLabelNode by minuting text into a UI text field as below.
I would ideally like the text to be updated as they type, one letter at a time. But I am a bit stuck as to how to do this?
At the moment I just have another button "labelNode2" which updates the text when pressed.
import SpriteKit
import GameplayKit
class GameScene: SKScene, UITextFieldDelegate {
private var field : UITextField?
lazy var labelNode = self.childNode(withName: "labelNode") as? SKLabelNode
lazy var labelNode2 = self.childNode(withName: "labelNode2") as? SKLabelNode
override func didMove(to view: SKView) {
labelNode?.text = "Test"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let largeLocation = touch.location(in: self)
let nodes = self.nodes(at: largeLocation)
for node in nodes {
if node.name == "labelNode" {
labelNode?.text = "hello"
if (field == nil) {
field = UITextField()
field?.frame = CGRect(x:100,y:100, width:100, height:30)
field?.delegate = self;
field?.backgroundColor = .red
field?.isHidden = true;
self.view?.addSubview(field!)
}
field?.text = labelNode?.text
field?.becomeFirstResponder
}
else {
field?.resignFirstResponder
//To hide keyboard
}
if node.name == "labelNode2" {
labelNode?.text = field?.text
}
}
}
}
}

First of all, I had to comment out your line field.isHidden = true.
Second of all, there are no semicolons in swift.
And finally... add this and you should be fine...
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//labelNode?.text = field?.text
labelNode?.text = (field?.text as NSString?)?.replacingCharacters(in: range, with: string)
return true
}
Why did I comment out that first line? Because the text change does not take effect until you return true...

Related

Create a hashtag in the text view swift

I found this question: I'm implementing a hashtag recognizer in UITextView but it never highlights two words with the same beginning?. By doing that I want to check the user's run time input text on the text view and each time user types a #hashtag, It will automatically highlight after adding a comma.
Code:-
import UIKit
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var txtView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
txtView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.resolveHashTags()
}
}
extension UITextView {
func resolveHashTags() {
let nsText = NSString(string: self.text)
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted)
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
for word in words {
if word.count < 3 {
continue
}
if word.hasPrefix("#") {
let matchRange:NSRange = nsText.range(of: word as String)
let stringifiedWord = word.dropFirst()
if let firstChar = stringifiedWord.unicodeScalars.first, NSCharacterSet.decimalDigits.contains(firstChar) {
} else {
attrString.addAttribute(NSAttributedString.Key.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
self.attributedText = attrString
}
}
Current ScreenShot:-
Question:- Can someone please explain to me how to highlight the text after adding a comma? I've tried to implement by above code but no results yet.
Any help would be greatly appreciated.
Thanks in advance.

Creation of buttons for SpriteKit

I am creating the main menu for a sprite kit application I am building. Throughout my entire project, I have used SKScenes to hold my levels and the actual gameplay. However, now I need a main menu, which holds buttons like "Play," "Levels," "Shop," etc... However, I don't feel really comfortable the way I am adding buttons now, which is like this:
let currentButton = SKSpriteNode(imageNamed: button) // Create the SKSpriteNode that holds the button
self.addChild(currentButton) // Add that SKSpriteNode to the SKScene
And I check for the touch of the button like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
for node in self.nodes(at: touchLocation) {
guard let nodeName = node.name else {
continue
}
if nodeName == ButtonLabel.Play.rawValue {
DispatchQueue.main.asyncAfter(deadline: .now()) {
let transition = SKTransition.reveal(with: .left, duration: 1)
self.view?.presentScene(self.initialLevel, transition: transition)
self.initialLevel.loadStartingLevel()
}
return
}
if nodeName == ButtonLabel.Levels.rawValue {
slideOut()
}
}
}
However, I don't know if this is considered efficient. I was thinking of using UIButtons instead, but for that would I have to use an UIView?
Or can I add UIButtons to an SKView (I don't really get the difference between an SKView, SKScene, and UIView) What is recommended for menus?
I totally agree with #Whirlwind here, create a separate class for your button that handles the work for you. I do not think the advice from #ElTomato is the right advice. If you create one image with buttons included you have no flexibility on placement, size, look and button state for those buttons.
Here is a very simple button class that is a subclass of SKSpriteNode. It uses delegation to send information back to the parent (such as which button has been pushed), and gives you a simple state change (gets smaller when you click it, back to normal size when released)
import Foundation
import SpriteKit
protocol ButtonDelegate: class {
func buttonClicked(sender: Button)
}
class Button: SKSpriteNode {
//weak so that you don't create a strong circular reference with the parent
weak var delegate: ButtonDelegate!
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(0.9)
self.delegate.buttonClicked(sender: self)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
}
}
This button can be instantiated 2 ways. You can create an instance of it in the Scene editor, or create an instance in code.
class MenuScene: SKScene, ButtonDelegate {
private var button = Button()
override func didMove(to view: SKView) {
if let button = self.childNode(withName: "button") as? Button {
self.button = button
button.delegate = self
}
let button2 = Button(texture: nil, color: .magenta, size: CGSize(width: 200, height: 100))
button2.name = "button2"
button2.position = CGPoint(x: 0, y: 300)
button2.delegate = self
addChild(button2)
}
}
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
You have to remember to make the scene conform to the delegate
class MenuScene: SKScene, ButtonDelegate
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
For simple scenes what you are doing is fine, and actually preferred because you can use the .SKS file.
However, if you have a complex scene what I like to do is subclass a Sprite and then override that node's touchesBegan.
Here is a node that I use in all of my projects... It is a simple "on off" button. I use a "pointer" to a Boolean via the custom Reference class I made, so that way this node doesn't need to be concerned with your other scenes, nodes, etc--it simply changes the value of the Bool for the other bits of code to do with what they want:
public final class Reference<T> { var value: T; init(_ value: T) { self.value = value } }
// MARK: - Toggler:
public final class Toggler: SKLabelNode {
private var refBool: Reference<Bool>
var value: Bool { return refBool.value }
var labelName: String
/*
var offText = ""
var onText = ""
*/
func toggleOn() {
refBool.value = true
text = labelName + ": on"
}
func toggleOff() {
refBool.value = false
text = labelName + ": off"
}
/*init(offText: String, onText: String, refBool: Reference<Bool>) {
ref = refBool
super.init(fontNamed: "Chalkduster")
if refBool.value { toggleOn() } else { toggleOff() }
isUserInteractionEnabled = true
}
*/
init(labelName: String, refBool: Reference<Bool>) {
self.refBool = refBool
self.labelName = labelName
super.init(fontNamed: "Chalkduster")
isUserInteractionEnabled = true
self.refBool = refBool
self.labelName = labelName
if refBool.value { toggleOn() } else { toggleOff() }
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if refBool.value { toggleOff() } else { toggleOn() }
}
public required init?(coder aDecoder: NSCoder) { fatalError("") }
override init() {
self.refBool = Reference<Bool>(false)
self.labelName = "ERROR"
super.init()
}
};
This is a more elaborate button than say something that just runs a bit of code when you click it.
The important thing here is that if you go this route, then you need to make sure to set the node's .isUserInteractionEnabled to true or it will not receive touch input.
Another suggestion along the lines of what you are doing, is to separate the logic from the action:
// Outside of touches func:
func touchPlay() {
// Play code
}
func touchExit() {
// Exit code
}
// In touches began:
guard let name = node.name else { return }
switch name {
case "play": touchPlay()
case "exit": touchExit()
default:()
}
PS:
Here is a very basic example of how to use Toggler:
class Scene: SKScene {
let spinnyNode = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
// This is the reference type instance that will be stored inside of our Toggler instance:
var shouldSpin = Reference<Bool>(true)
override func didMove(to view: SKView) {
addChild(spinnyNode)
spinnyNode.run(.repeatForever(.rotate(byAngle: 3, duration: 1)))
let toggleSpin = Toggler(labelName: "Toggle Spin", refBool: shouldSpin)
addChild(toggleSpin)
toggleSpin.position.y += 100
}
override func update(_ currentTime: TimeInterval) {
if shouldSpin.value == true {
spinnyNode.isPaused = false
} else if shouldSpin.value == false {
spinnyNode.isPaused = true
}
}
}

userDefaults saving level data as array not saving

I have a game and I have custom levels which is saved as an array. I use the function saveCustomLevels to save the value of the level data. Every time the level data is changed, I do saveCustomLevels(). Whenever I do saveCustomLevels() and close the program and restart it the level data is always not saved and back to the default data. Am I doing something wrong. This is my code.
//in different class
func createLevels{
customLevels = (0...8).map {_ in level}
}
//in regular class
func saveCustomLevels(){
var customDefault = UserDefaults.standard
customDefault.synchronize()
}
override func didMove(to view: SKView) {
var customDefault = UserDefaults.standard
if customDefault.value(forKey: "Custom") != nil
customLevels = customDefault.value(forKey: "Custom") as! Array<Array<square>>
}
square.createLevels()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
}
if squares[i].node.contains(location){
customLevels[zzz][s].startColor = colors[i]
saveCustomLevels()
}

Callback for MSSticker Peels in iOS 10 iMessage sticker app

I'm experimenting with sticker iMessage apps in iOS 10 and I'm running into an issue with the override func didStartSending(_ message: MSMessage, conversation: MSConversation) method in MSMessagesAppViewController. When "peeling" a sticker from an MSStickerView, I would expect to receive some sort of callback on the didStartSending method. But it appears this is not the case. Does anyone know if this is the expected behavior and/or if there's another way to subscribe to callbacks for when these stickers are peeled, dragged, and dropped into the MSConversation? I realize that didStartSending is reserved for when the user taps the send button, but surely there should be some way of knowing when users drag MSStickers without hacking together some UIView dragging/rect-reading heuristic.
Messages View Controller:
class MessagesViewController: MSMessagesAppViewController {
var nYCStickersBroswerViewController: NYCStickersBroswerViewController!
override func viewDidLoad() {
super.viewDidLoad()
nYCStickersBroswerViewController = NYCStickersBroswerViewController(stickerSize: .regular)
nYCStickersBroswerViewController.view.frame = self.view.frame
self.addChildViewController(nYCStickersBroswerViewController)
nYCStickersBroswerViewController.didMove(toParentViewController: self)
self.view.addSubview(nYCStickersBroswerViewController.view)
nYCStickersBroswerViewController.loadStickers()
nYCStickersBroswerViewController.stickerBrowserView.reloadData()
}
...
override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
// Called when the user taps the send button.
print(message) // should this not contain the sticker that is peeled, dragged, and dropped into the conversation?
}
}
Sticker Browser:
import Foundation
import UIKit
import Messages
class ASSticker: MSSticker {
var identifier: String?
}
class NYCStickersBroswerViewController: MSStickerBrowserViewController {
var stickers = [ASSticker]()
override func viewDidLoad() {
super.viewDidLoad()
}
func changeBrowswerViewBackgroundColor(color: UIColor) {
stickerBrowserView.backgroundColor = color
}
func loadStickers() {
createSticker(name: "brooklyn", localizedDescription: "Brooklyn Bridge Sticker")
createSticker(name: "liberty", localizedDescription: "Statue of Liberty Sticker")
createSticker(name: "love", localizedDescription: "I Love New York Sticker")
createSticker(name: "mets", localizedDescription: "New York Mets Sticker")
createSticker(name: "rangers", localizedDescription: "New York Rangers Sticker")
createSticker(name: "subway", localizedDescription: "New York City MTA Subway Train Sticker")
}
func createSticker(name: String, localizedDescription: String) {
guard let stickerPath = Bundle.main.pathForResource(name, ofType: "png") else {
print("Call ae cab, you're intoxicated.")
return
}
let stickerURL = URL(fileURLWithPath: stickerPath)
let sticker: ASSticker
do {
try sticker = ASSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
sticker.identifier = "something unique"
stickers.append(sticker)
} catch {
print("Call a cab, you're intoxicated.")
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return self.stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return self.stickers[index]
}
}
Here's a subclass and delegate that will tie into the tap and long press gesture recognizers that MSStickerView is using for select and peel interactions. If the implementation of MSStickerView changes this may no longer provide events, but shouldn't crash.
import UIKit
import Messages
protocol InstrumentedStickerViewDelegate: class {
func stickerViewDidSelect(stickerView: MSStickerView)
func stickerViewDidPeel(stickerView: MSStickerView)
}
class InstrumentedStickerView: MSStickerView {
weak var delegate: InstrumentedStickerViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
for gestureRecognizer in gestureRecognizers ?? [] {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer {
tapGestureRecognizer.addTarget(self, action: #selector(didTap))
} else if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer {
longPressGestureRecognizer.addTarget(self, action: #selector(didLongPress))
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func didTap(tapGestureRecognizer: UITapGestureRecognizer) {
if tapGestureRecognizer.state == .Recognized {
delegate?.stickerViewDidSelect(self)
}
}
func didLongPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .Began {
delegate?.stickerViewDidPeel(self)
}
}
}
This is a workaround for sticker peeled and tapped events, it is not guaranteed that a particular sticker will be inserted but an approximation of such data points - you will have to use your subclass of MSStickerView. This solution is not futureproof, but works for the time being IMHO, therefore I welcome other ideas.
import UIKit
import Messages
class CustomStickerView : MSStickerView{
class GestureRecognizerReceiver : NSObject, UIGestureRecognizerDelegate{
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
let _recognizerDelegate = GestureRecognizerReceiver()
weak var _recognizer: UITapGestureRecognizer? = nil
func setupTapRecognizer(){
if _recognizer == nil {
let r = UITapGestureRecognizer(target: self, action: #selector(_customTapReceived))
r.cancelsTouchesInView = false
r.delegate = _recognizerDelegate
addGestureRecognizer(r)
_recognizer = r
}
}
func _customTapReceived(){
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Inserted", description: s.localizedDescription)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Peeled", description: s.localizedDescription)
}
}
}
Usage:
let sv = CustomStickerView(frame: _stickerViewHolder.bounds, sticker: sticker)
sv.setupTapRecognizer()
_stickerViewHolder.addSubview(sv)
sv.startAnimating()

Multiple circles drawn SpriteKit

I'm working on simple game which will based on paiting background by player. In left and right corner there will be buttons which will move character to left or right. I've already implemented that (character is moving and lefts painted background behind), but with adding another circles fps's drops really fast. Is there any solution to that?
import SpriteKit
class GameScene: SKScene {
var playerDot:PlayerDot = PlayerDot(imageNamed:"player")
var isTurningLeft:Bool = false
var isTurningRight:Bool = false
var lastLocation:CGPoint = CGPoint()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Helvetica")
myLabel.name = "left"
myLabel.text = "Left"
myLabel.fontSize = 30
myLabel.horizontalAlignmentMode = .Left
myLabel.position = CGPoint(x:CGRectGetMinX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel)
let myLabel2 = SKLabelNode(fontNamed:"Helvetica")
myLabel2.name = "right"
myLabel2.text = "Right"
myLabel2.fontSize = 30
myLabel2.horizontalAlignmentMode = .Right
myLabel2.position = CGPoint(x:CGRectGetMaxX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel2)
playerDot.position = CGPoint(x:CGRectGetMaxX(self.frame)/2, y:CGRectGetMinY(self.frame))
self.addChild(playerDot)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = true
isTurningRight = false
}
else if theName == "right" {
isTurningRight = true
isTurningLeft = false
}
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func update(currentTime: CFTimeInterval) {
if(isTurningLeft){
playerDot.increaseAngle()
} else if (isTurningRight){
playerDot.decreaseAngle()
}
//calculates new character position based on angle of movement changed
playerDot.updatePosition()
drawCircle()
}
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let circle = SKShapeNode(circleOfRadius: 10 )
circle.position = playerDot.position
circle.fillColor = SKColor.orangeColor()
circle.strokeColor = SKColor.orangeColor()
self.addChild(circle)
lastLocation = playerDot.position
}
}
func distanceFromCGPoints(a:CGPoint,b:CGPoint)->CGFloat{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
}
EDIT:
drawCircle with SKShapeNode replaced with SKSpriteNote
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(imageNamed:"player")
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
If you are having frame rate drops then some parts of your code are slowing down execution too much. You need to optimise your code. Use Instruments (Time Profiler) to find out which lines/functions are causing problems speed wise. If you have never used it before read up on it and use it a couple times to get the gist, then I recommend watching the WWDC video Profiling In-Depth
Based on your edit (switching to SKSpriteNode), this is how you could do it:
Declare a texture as a property of your scene. This way, you load it once and reuse it later:
let yourTexture = SKTextureAtlas(named: "yourAtlas").textureNamed("yourTexture")
Then in your drawCircle method:
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(texture: yourTexture)
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
This way (by using SKSpriteNode instead of SKShapeNode) you have reduced number of draw calls required to render all those circles. Also because you are reusing the same texture, instead of allocating it every time using imageNamed you have reduced greatly an amount of memory that app consumes. Now, SpriteKit can render hundreds of nodes #60 fps if those nodes are rendered in batches. Still there is a limit before fps start dropping. And that depends on a device.