Initially I had this code working when I was just animating the one UIImageView that I had. But then I changed it to animate several dynamically created UIImageViews, however since they are dynamically created inside a for loop, I'm finding it difficult to animate them as I did the initial one.
override func viewDidLoad() {
super.viewDidLoad()
var sprite: UIImage = UIImage(named: "sprites/areaLocatorSprite.png")!
var locations:NSArray = animal[eventData]["locations"] as NSArray
for var i = 0; i < locations.count; i++ {
println(locations[i]["locationx"])
var locationx = locations[i]["locationx"] as String
var locationy = locations[i]["locationy"] as String
let x = NSNumberFormatter().numberFromString(locationx)
let y = NSNumberFormatter().numberFromString(locationy)
let cgfloatx = CGFloat(x!)
let cgfloaty = CGFloat(y!)
var mapSprite: UIImageView
mapSprite = UIImageView(image: sprite)
mapSprite.frame = CGRectMake(cgfloatx,cgfloaty,10,10)
townMap.addSubview(mapSprite)
timer = NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: Selector("flash"), userInfo: nil, repeats: true)
}
}
func flash() {
var mapSprite:UIImageView?
if mapSprite?.alpha == 1 {
mapSprite?.alpha = 0
} else {
mapSprite?.alpha = 1
}
}
This does not work as the mapSprite in the flash function is different to the one in the for loop. How can I refer to the one in the for loop and then animate it? Or would there be a better alternative to what I'm currently doing?
Many thanks!
EDIT
Using Xcode 6.2
You need to store the views into a property and then enumerate that property each time your timer event is fired
var sprites: [UIImageView]?
override func viewDidLoad() {
super.viewDidLoad()
var sprite = UIImage(named: "sprites/areaLocatorSprite.png")!
var locations:NSArray = animal[eventData]["locations"] as NSArray
self.sprites = map(locations) {
var locationx = $0["locationx"] as String
var locationy = $0["locationy"] as String
let x = NSNumberFormatter().numberFromString(locationx)
let y = NSNumberFormatter().numberFromString(locationy)
let cgfloatx = CGFloat(x!)
let cgfloaty = CGFloat(y!)
var mapSprite = UIImageView(image: sprite)
mapSprite.frame = CGRectMake(cgfloatx,cgfloaty,10,10)
townMap.addSubview(mapSprite)
return mapSprite
}
timer = NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: Selector("flash"), userInfo: nil, repeats: true)
}
func flash() {
if let sprites = self.sprites {
for sprite in sprites {
sprite.alpha = sprite.alpha == 0 ? 1 : 0
}
}
}
Related
I'm trying to get the values from 4 different variables in PrefsViewController in ViewController but when PrefsViewController is dismissed the values reset.
Snippet of ViewController.swift
class ViewController: NSViewController, NSWindowDelegate {
var triesEnabled = false
var minNumber = 0
var maxNumber = 20
var maxTries = 3
#objc func applyPrefs() {
let mainSB = NSStoryboard(name: "Main", bundle: nil)
let prefsVC: PrefsViewController = mainSB.instantiateController(withIdentifier: "prefsViewController") as! PrefsViewController
minNumber = prefsVC.prefsMinNum
maxNumber = prefsVC.prefsMaxNum
triesEnabled = prefsVC.prefsTriesEnabled
maxTries = prefsVC.prefsMaxTries
print("\(minNumber) \(maxNumber) \(triesEnabled) \(maxTries)") // here I can see that it has been reset to the default values
resetGame(minRange: minNumber, maxRange: maxNumber, isTriesEnabled: triesEnabled, triesLimit: maxTries)
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(applyPrefs), name: Notification.Name("notifyApplyPrefs"), object: nil )
resetGame(minRange: minNumber, maxRange: maxNumber, isTriesEnabled: triesEnabled, triesLimit: maxTries)
}
}
Snippet of PrefsViewController
class PrefsViewController: NSViewController {
var triesOn = false
var prefsMinNum: Int = 0
var prefsMaxNum: Int = 20
var prefsTriesEnabled: Bool = false
var prefsMaxTries: Int = 3
#IBAction func closeButton(_ sender: Any) {
if (!minNumTextField.stringValue.isInt || !maxNumTextField.stringValue.isInt || !triesTextField.stringValue.isInt) {
let alert = NSAlert()
alert.messageText = "Error"
alert.informativeText = "You can only enter (whole) numbers in the text fields."
alert.alertStyle = .warning
alert.addButton(withTitle: "OK")
alert.runModal()
return
}
prefsMinNum = Int(minNumTextField.intValue)
prefsMaxNum = Int(maxNumTextField.intValue)
prefsTriesEnabled = triesOn
prefsMaxTries = Int(minNumTextField.intValue)
print("minNumTextField: \(prefsMinNum) maxNumTextField: \(prefsMaxNum) triesCheckBox: \(prefsTriesEnabled) triesTextField: \(prefsMaxTries)")
NotificationCenter.default.post(name: Notification.Name("notifyApplyPrefs"), object: nil)
self.dismiss(self)
}
}
The prefs view is shown as a sheet (I don't know if that's important)
When you use instantiateController you're creating a new, never before used, instance of the controller. If you want to have the data transferred using a notification, send self as the object when you call post and then use that to get your variables.
You would also need to change applyPrefs so that it takes a Notification parameter so that the notification object is available to you.
I'm working with counting label String. When I run my project all my text shows and at the end it crashes with:
'Fatal error: String index is out of bounds'
on the line:
startText += String(endText[index])
in the handleUpdate method.
I don't understand why.
class AboutViewController: BaseListController {
let countingLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(countingLabel)
countingLabel.frame = view.frame
let displayLink = CADisplayLink(target: self, selector: #selector(handleUpdate))
displayLink.add(to: .main, forMode: .default)
}
var startText = " "
let endText = "Hey! I need your help..."
var startValue = 0
#objc func handleUpdate(){
let endTextValue = endText.count - 1
let index = endText.index(endText.startIndex, offsetBy: startValue)
self.countingLabel.text = "\(startText)"
// Error on the following line:
startText += String(endText[index])
startValue += 1
if startValue > endTextValue{
startText = endText
}
print(endTextValue)
}
let animationStartDate = Date()
}
You are calling handleUpdate on a CADisplayLink, it will continue to be called after startValue has been incremented beyond the length of the string. You do have a check for startValue > endTextValue but this is after you have already tried to index using startValue, so you get a crash.
You need to remove the CADisplayLink handler once its job is done, which means you need to keep a reference to it.
var startText = ""
let endText = "Hey! I need your help..."
var startValue = 0
var displayLink: CADisplayLink?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(countingLabel)
countingLabel.frame = view.frame
self.displayLink = CADisplayLink(target: self, selector: #selector(handleUpdate))
self.displayLink!.add(to: .main, forMode: .default)
}
#objc func handleUpdate(){
guard startValue < endText.count else {
self.displayLink?.invalidate()
self.displayLink = nil
return
}
let index = endText.index(endText.startIndex, offsetBy: startValue)
startText += String(endText[index])
self.countingLabel.text = "\(startText)"
startValue += 1
let animationStartDate = Date()
}
I'm trying to make a choose-your-own adventure game that changes the text of two labels (the user choices) depending on which label the user taps. I figured I would just do a very nested if-else statement rather than bother with trying to implement a binary tree. I know how to attach the gesture recognizer to a label (I think):
let tapped1 = UITapGestureRecognizer(target: self, action: #selector(VCGame.usrChose1))
choice1.isUserInteractionEnabled = true
choice1.addGestureRecognizer(tapped1)
let tapped2 = UITapGestureRecognizer(target: self, action: #selector(VCGame.usrChose2))
choice2.isUserInteractionEnabled = true
choice2.addGestureRecognizer(tapped2)
and I can define what to do when the label is touched in the usrChose1 and usrChose2 functions, however, those functions only work once: the first time the function is chosen and my game has more than just one choice. From there, the labels will just do the same thing if the user touches them.
How would I go about having a condition inside the if-else statement that evaluates to true or false if label1 or label2 is tapped?
Here's the code for usrChoice1 and usrChoice2, for clarification
func usrChose1(sender : UITapGestureRecognizer) {
print("tap 1 working")
choice1.text = "choice1.1"
choice2.text = "choice1.2"
}
func usrChose2(sender : UITapGestureRecognizer) {
print("tap2 working")
choice1.text = "update2.1";
choice2.text = "update2.2"
}
Below image shows my requirement :
According to your requirement, I have tried the following:
I have made a dummy project with two labels inside a view controller
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var choice1Label: UILabel!
#IBOutlet weak var choiceLabel2: UILabel!
var tapStart: Bool = false
var levelType1: Level?
var levelType2: Level?
override func viewDidLoad() {
super.viewDidLoad()
let tapped1 = UITapGestureRecognizer(target: self, action: #selector(usrChose1))
choice1Label.isUserInteractionEnabled = true
choice1Label.addGestureRecognizer(tapped1)
let tapped2 = UITapGestureRecognizer(target: self, action: #selector(usrChose2))
choiceLabel2.isUserInteractionEnabled = true
choiceLabel2.addGestureRecognizer(tapped2)
setup()
}
var currentLevel1: Level?
var currentLevel2: Level?
func setup() {
let lb2Child1Child1 = Level(text: "2.1.1", subLevels: nil)
let lb2Child1Child2 = Level(text: "2.1.2", subLevels: nil)
let lb1Child1Child1 = Level(text: "1.1.1", subLevels: nil)
let lb1Child1Child2 = Level(text: "1.1.2", subLevels: nil)
let lb1Child2Child1 = Level(text: "1.2.1", subLevels: nil)
let lb1Child2Child2 = Level(text: "1.2.2", subLevels: nil)
let lb1Child1 = Level(text: "1.1", subLevels: [lb1Child1Child1, lb1Child1Child2])
let lb1Child2 = Level(text: "1.2", subLevels: [lb1Child2Child1, lb1Child2Child2])
let lb2Child1 = Level(text: "2.1", subLevels: [lb2Child1Child1, lb2Child1Child2])
let lb2Child2 = Level(text: "2.2", subLevels: nil)
levelType1 = Level(text: "1", subLevels: [lb1Child1, lb1Child2])
levelType2 = Level(text: "2", subLevels: [lb2Child1, lb2Child2])
choice1Label.text = levelType1!.text ?? ""
choiceLabel2.text = levelType2!.text ?? ""
}
func usrChose1(sender : UITapGestureRecognizer) {
if !tapStart {
currentLevel1 = levelType1
tapStart = true
}
if let subLevelsArray = currentLevel1?.subLevels {
print(subLevelsArray[0].text ?? "")
print(subLevelsArray[1].text ?? "")
choice1Label.text = subLevelsArray[0].text ?? ""
choiceLabel2.text = subLevelsArray[1].text ?? ""
currentLevel1 = subLevelsArray[0]
currentLevel2 = subLevelsArray[1]
}
}
func usrChose2(sender : UITapGestureRecognizer) {
//print("tap2 working")
// choice1Label.text = "update2.1";
//choiceLabel2.text = "update2.2"
if !tapStart {
currentLevel2 = levelType2
tapStart = true
}
if let subLevelsArray = currentLevel2?.subLevels {
print(subLevelsArray[0].text ?? "")
print(subLevelsArray[1].text ?? "")
choice1Label.text = subLevelsArray[0].text ?? ""
choiceLabel2.text = subLevelsArray[1].text ?? ""
currentLevel1 = subLevelsArray[0]
currentLevel2 = subLevelsArray[1]
}
}
}
I have made a class named Level to represent a single level and each level contains sublevels
Level.swift
import UIKit
class Level {
var text: String?
var subLevels: [Level]?
init(text: String, subLevels: [Level]?) {
self.text = text
self.subLevels = subLevels ?? nil
}
}
You have to add UITapGestureRecognizer in UILabel or UIView whatever is container.
Add 2 different Int variables in each functions usrChose1 and usrChose2 respectively, which will be work as a counter.
var i = 0
var j = 0
func usrChose1(_ recognizer: UITapGestureRecognizer) {
i++
print("Total clicked label 1 :::",i)
}
func usrChose2(_ recognizer: UITapGestureRecognizer) {
j++
print("Total clicked label 2 :::",j)
}
I'm using script (below), to use as a countdown for my game start, the script I've used is from Gourav Nayyar's YouTube video and works great for the first time it is called. However once the game goes through the reset process and the script is called again I only see 5 rather than 5 - 4 - 3 - 2 - 1 - GO!. If I remove one of the cals from my script then it works fine either in the reset func or when gameScene loads.
Here is the two calls in GameScene.swift
override func didMoveToView(view: SKView) {
var gamelaunchTimerView:TimerView = TimerView.loadingCountDownTimerViewInView(self.view!)
gamelaunchTimerView.startTimer()
func resetScene() {
//code removed from here
return countdown()
}
func countdown() {
var gamelaunchTimerView:TimerView = TimerView.loadingCountDownTimerViewInView(self.view!)
gamelaunchTimerView.startTimer()
}
Here is the Timer Code in GameLaunchTimer.swift as this is set up the countdown only works when first called and hangs on the second call.
//
// TimerView.swift
// GameLaunchTimer
//
// Created by Gourav Nayyar on 7/3/14.
// Copyright (c) 2014 Gourav Nayyar. All rights reserved.
//
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
import UIKit
import QuartzCore
class TimerView :UIView {
struct Stored {
static var timerLbl:UILabel!
}
class func loadingCountDownTimerViewInView (_superView:UIView)-> TimerView
{
var timerView:TimerView = TimerView(frame:_superView.frame)
// timerView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(VIEW_ALPHA)
_superView.addSubview(timerView)
/* add a custom Circle view */
let refFrame:CGRect = CGRectMake(_superView.center.x-TIMERVIEW_RADIUS, _superView.center.y-TIMERVIEW_RADIUS, 2*TIMERVIEW_RADIUS, 2*TIMERVIEW_RADIUS)
var circleView:UIView = UIView(frame:refFrame)
circleView.layer.cornerRadius = TIMERVIEW_RADIUS
circleView.layer.borderColor = UIColor.whiteColor().CGColor
circleView.layer.borderWidth = BORDER_WIDTH
/* add a custom Label */
Stored.timerLbl = UILabel(frame:circleView.bounds)
Stored.timerLbl.text = "\(TIMER_LABEL_INITIAL_VAL)"
Stored.timerLbl.textColor = UIColor.whiteColor()
Stored.timerLbl.font = UIFont(name: "MarkerFelt-Thin", size: 40)
Stored.timerLbl.textAlignment = NSTextAlignment.Center
circleView.addSubview(Stored.timerLbl)
timerView.addSubview(circleView)
return timerView
}
func startTimer()
{
timer = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt:NSTimer)
{
timerVal--
if timerVal==0{
Stored.timerLbl.text = "GO!"
}else if timerVal<0{
timer.invalidate()
removeCountDownTimerView()
} else{
Stored.timerLbl.text = "\(timerVal)"
}
}
func removeCountDownTimerView()
{
var mySuperView:UIView = self.superview!
mySuperView.userInteractionEnabled = true
super.removeFromSuperview()
}
}
Define your variables under the class body;
import UIKit
import QuartzCore
class TimerView :UIView {
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
... // other code
or, may be
let VIEW_ALPHA:CGFloat = 0.5
let TIMERVIEW_RADIUS:CGFloat = 50
let TIMER_LABEL_INITIAL_VAL:Int = 5
let BORDER_WIDTH:CGFloat = 2
import UIKit
import QuartzCore
class TimerView :UIView {
var timerVal:Int = TIMER_LABEL_INITIAL_VAL;
var timer:NSTimer!
... //other code
Assign nil to the timer after invalidating it: even though you are invalidating, the object state is still kept, resulting on states conflicts when creating a new instance of the timer, once that it runs in a different thread.
Original code using join() shows a memory leak in Instruments. I used a loop instead and it seems to have fixed the issue. Curious if this seems to be a framework issue?
//in ViewDidLoad
let saveButton = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: "saveBtnAc")
self.navigationItem.rightBarButtonItem = saveButton
//end ViewDidLoad
//using a loop to join the string instead of join()
func doJndString(theArray: [String]) -> String {
var updatedAryJnd = String()
var count = 0
let lastItemCount = theArray.count - 1
while (count < lastItemCount) {
updatedAryJnd += (theArray[count] + "§")
count++
}
updatedAryJnd += theArray[lastItemCount]
return updatedAryJnd
}
//button action
func saveBtnAc() {
var myAry = [""]
if let items1 = setupItem?.valueForKey("itemsAry1") as? String {
myAry = items1.componentsSeparatedByString("§")
}
myAry[itemClkdVar] = myTxtOutlet.text
//I get a leak when using the following join()
//let updatedAryJnd = "§".join(myAry)
//setupItem!.setValue(updatedAryJnd, forKey: "itemsAry1")
//change above to this and no leak
setupItem!.setValue(doJndString(myAry), forKey: "itemsAry1")
var error: NSError? = nil
if !contextOb!.save(&error) {
println("had an error")
}
}