Lag using runAction with a SKLabelNode on swift - ios

I am having a lag issue with this function that is used a lot of times in my app...
plusOne(scorelabel.position,plus: 1)
And:
func plusOne(position: CGPoint, plus : Int) {
myLabel.setScale(1)
myLabel.text = "+"+String(plus)
myLabel.position = position
myLabel.hidden = false
let action1 = SKAction.scaleTo(2, duration: 0.5)
let action2 = SKAction.fadeOutWithDuration(0.5)
let actionGroup = SKAction.group([action1,action2])
myLabel.runAction(actionGroup,completion: {
self.myLabel.hidden = true
})
}
The first time I use the plusOne function, always make my app be freezed for a little time...
I do not know if I have been doing the things well... myLabel has been declared global but it is the same... always with lag on the first execution.

You need to set the font of your label with a fix font at start.
Like that:
let yourFont = UIFont(name: "yourfontName", size: 17)
var myLabel = SKLabelNode(fontNamed: yourFont?.fontName)
Otherwise, your font gets loaded at the first usage and not on app-start.

Related

Smoothly pause a repeating UIView block animation on alpha before another animation

I've read many Stack Overflow questions in search of a solution for this, but I can't find one that addresses this particular issue in a way that works in iOS 13.
I have a label that I want to pulse repeatedly (like a slow, fading blink) once my view controller loads.
When it's time to transition away from the view, at the push of a button, I want to stop the pulsing at whatever alpha the pulsing label currently has, and then fade it out from there along with other UI elements.
In the past, I would have used a UIViewPropertyAnimator for this repeating animation, and then paused the animation once the button is tapped, but as of iOS 13 UIView.setAnimationRepeatCount(.greatestFiniteMagnitude) and UIView.setAnimationRepeatAutoreverses(true) are deprecated, which (I think) makes it impossible to build my pulse animation with a property animator.
Here is my example code:
import UIKit
class ViewController: UIViewController {
private lazy var pulsingLabel: UILabel = {
let label = UILabel()
label.text = "I am pulsing"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private lazy var stopButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Stop Pulsing",
for: .normal)
button.addTarget(self,
action: #selector(fadeEverythingOut),
for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pulsingLabel)
view.addSubview(stopButton)
pulsingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
pulsingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stopButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stopButton.firstBaselineAnchor.constraint(equalToSystemSpacingBelow: pulsingLabel.lastBaselineAnchor,
multiplier: 4).isActive = true
startRepeatingAnimation()
}
private func startRepeatingAnimation() {
UIView.animate(withDuration: 1,
delay: 0,
options: [.repeat, .autoreverse, .curveEaseInOut],
animations: {
self.pulsingLabel.alpha = 0
})
}
#objc
private func fadeEverythingOut() {
pulsingLabel.layer.removeAllAnimations()
UIView.animate(withDuration: 2.0,
animations: {
self.pulsingLabel.alpha = 0
})
}
}
In this example, I'm calling pulsingLabel.layer.removeAllAnimations() to stop the animation, but this is not an acceptable solution because the label just immediately disappears when it's called.
I've also tried some weird stuff like sandwiching that line between UIView.setAnimationsEnabled(false) and UIView.setAnimationsEnabled(true), but that didn't work.
I also tried saving the alpha of pulsingLabel right before calling pulsingLabel.layer.removeAllAnimations() and then setting it again right after, but I always get a value of 0 for pulsingLabel.alpha.
Am I missing something here by abandoning UIViewPropertyAnimator, where I would still be able to get it to repeat, even with the deprecated functions on UIView? Or maybe I need to be calling .layer.removeAllAnimations() on a different layer or differently somehow?
Any ideas would be much appreciated, as I care quite a bit about getting these fine details right in my app!
I didn't see that you were looking for swift answer, but I only know ObjC right now. So here is the solution by timer (though in ObjC sorry):
In viewdidload:
self.angle = 0;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(update) userInfo:nil repeats:YES];
angle is CGFloat to store current value to calculate the alpha by cosine function in update method:
-(void)update {
self.angle += M_PI/36.0;
if (self.angle > M_PI) self.angle = 0.0;
CGFloat val = (1 + cos(self.angle)) / 2.0;
dispatch_async(dispatch_get_main_queue(), ^{
self.myLabel.alpha = val;
});
}
You can adjust the value 36.0 to make the fade in/out slower or faster. val will give a swing value between 0 and 1 back and forth, as angle increases. Then using GCD we set the label alpha as needed in real time. When a button is pressed to move to another viewcontroller (as per your requirement), you only need to stop the timer by invalidating it, and the label will remain at whatever alpha is was set.
[self.myTimer invalidate];
self.myTimer = nil;
// CGFloat currentAlpha = self.myLabel.alpha;
From here, you can do other thing with label alpha.

Memory increase when returning to GameScene

I'm creating a game which consists of multiple scenes but my problem is in the game scene. When I start playing and then return to the menu scene I notice that memory isn't being freed, instead memory keeps increasing every time I go to the game scene and eventually it crashes.
I've already tried to remove all actions and children from self in the function 'willMove', like this:
override func willMove(from view: SKView) {
self.removeAllActions()
self.removeAllChildren()
}
But it did nothing.
I believe my problem is that I have too many animations made with SKActions like these:
//example 1, whiteCircle is an SKShapeNode
whiteCircle.run(.sequence([.scale(to: 1.5, duration: 0.5), .removeFromParent()]))
//example 2, SKAction.colorize doesn't work with SKLabels so I did this
let color1 = SKAction.run {
label.fontColor = .red
}
let color2 = SKAction.run {
label.fontColor = .yellow
}
let color3 = SKAction.run {
label.fontColor = .blue
}
let color4 = SKAction.run {
label.fontColor = .green
}
let wait = SKAction.wait(forDuration: 0.2)
label.run(SKAction.repeatForever(SKAction.sequence([color1, wait, color2, wait, color3, wait, color4, wait])))
//example 3, this is to refresh the label's text and change it's color
coinLabel.run(SKAction.sequence([.wait(forDuration: 3.25), .run {
self.coinLabel.fontColor = .yellow
self.coinLabel.text = "\(UserDefaults.standard.integer(forKey: "coins"))"
}]))
I'm using lots of images as SKTextures too, used like this:
coinIcon.texture = SKTexture(image: UIImage(named: "coinImage")!)
My variables are all declared like this in the GameScene class:
var ground = SKSpriteNode()
var ceiling = SKSpriteNode()
var character = SKSpriteNode()
var scoreLabel = SKLabelNode()
var coinLabel = SKLabelNode()
var coinIcon = SKLabelNode()
I think I may be creating a strong reference cycle, but I don't know where or how to identify it, I'm new to SpriteKit so sorry if my question seems dumb. Any help is very much appreciated.
It's hard to actually give you a solution not being able to debug with you and see how you are managing references in your Game, but I've encoutered this issue and I was able to solve it using those two things:
deinit {
print("(Name of the SKNode) deinit")
}
Using this, I could find which objects the arc could not remove reference.
let color4 = SKAction.run { [weak self] in
self?.label.fontColor = .green
}
This one helped me clean all strong references in my Game. For many of us, it’s best practice to always use weak combined with self inside closures to avoid retain cycles. However, this is only needed if self also retains the closure. More Information.

Swift: How to unwrap UITextField Optional String to Float

I'm making an app, specifically a tip calculator, to practice my Swift skills. Currently using Swift 5.
I've set up the layout, labels, textfields, etc. But one problem I am running into is the optional string from the user input in text field box. I am trying to multiply the user input (String?) with the slider value, which is a Float.
I've tried several ways, force unwrapping, guard statements, if let, and type casting, but everything I get something similar where Xcode won't let me mix two types together. I've also gotten the text field input to an optional Float (Float?) but still, Xcode wants me to find a way to get the two types together.
My text field input box is a variable closure.
private let priceTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "$00.00"
textField.textColor = UIColor.black
textField.font = UIFont.init(name: "HelveticaNeue-Thin", size: 60)
textField.keyboardType = UIKeyboardType.numberPad
textField.textAlignment = .center
textField.borderStyle = UITextField.BorderStyle.roundedRect
textField.borderStyle = UITextField.BorderStyle.none
textField.sizeToFit()
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
This is my slider action function that will calculate the tip amount.
#objc func sliderChange() {
totalTipAmount.text = (tipSlider.value) * Float(priceTextField.text! as NSString)
myTipTextView.text = "My Tip (\(Int(tipSlider.value.rounded()))%):"
}
Your calculation seems fine but it might be throwing a compiler error for setting Float in place of String to totalTipAmount. You can try as follows,
#objc func sliderChange() {
guard let priceText = self.priceTextField.text, let price = Float(priceText) else { return }
totalTipAmount.text = "\(tipSlider.value * price)"
}

Can't Get iOS Print Renderer to Draw Properly

OK. There seems to be a dearth of examples on this, and I am fairly stumped.
I'm trying to make a custom print page renderer; the type that completely customizes the output, not one that uses an existing view.
The really weird thing, is that I was able to do this in ObjC a couple of years ago, and I can't seem to do the same thing in Swift.
I should mention that I am using the prerelease (Beta 5) of Xcode, and Swift 4 (Which has almost no difference at all from Swift 3, in my project).
The project is here.
It's a completely open-source project, so nothing's hidden; however, it's still very much under development, and is a moving target.
This is the page renderer class.
BTW: Ignore the delegate class. I was just thrashing around, trying to figure stuff up. I'm not [yet] planning on doing any delegate stuff.
In particular, my question concerns what's happening here:
override func drawContentForPage(at pageIndex: Int, in contentRect: CGRect) {
let perMeetingHeight: CGFloat = self.printableRect.size.height / CGFloat(self.actualNumberOfMeetingsPerPage)
let startingPoint = max(self.maxMeetingsPerPage * pageIndex, 0)
let endingPointPlusOne = min(self.maxMeetingsPerPage, self.actualNumberOfMeetingsPerPage)
for index in startingPoint..<endingPointPlusOne {
let top = self.printableRect.origin.y + (CGFloat(index) * perMeetingHeight)
let meetingRect = CGRect(x: self.printableRect.origin.x, y: top, width: self.printableRect.size.width, height: perMeetingHeight)
self.drawMeeting(at: index, in: meetingRect)
}
}
and here:
func drawMeeting(at meetingIndex: Int, in contentRect: CGRect) {
let myMeetingObject = self.meetings[meetingIndex]
var top: CGFloat = contentRect.origin.y
let topLabelRect = CGRect(x: contentRect.origin.x, y: 0, width: contentRect.size.width, height: self.meetingNameHeight)
top += self.meetingNameHeight
let meetingNameLabel = UILabel(frame: topLabelRect)
meetingNameLabel.backgroundColor = UIColor.clear
meetingNameLabel.font = UIFont.boldSystemFont(ofSize: 30)
meetingNameLabel.textAlignment = .center
meetingNameLabel.textColor = UIColor.black
meetingNameLabel.text = myMeetingObject.name
meetingNameLabel.draw(topLabelRect)
}
Which is all called from here:
#IBAction override func actionButtonHit(_ sender: Any) {
let sharedPrintController = UIPrintInteractionController.shared
let printInfo = UIPrintInfo(dictionary:nil)
printInfo.outputType = UIPrintInfoOutputType.general
printInfo.jobName = "print Job"
sharedPrintController.printPageRenderer = BMLT_MeetingSearch_PageRenderer(meetings: self.searchResults)
sharedPrintController.present(from: self.view.frame, in: self.view, animated: false, completionHandler: nil)
}
What's going on, is that everything on a page is being piled at the top. I am trying to print a sequential list of meetings down a page, but they are all getting drawn at the y=0 spot, like so:
This should be a list of meeting names, running down the page.
The way to get here, is to start the app, wait until it's done connecting to the server, then bang the big button. You'll get a list, and press the "Action" button at the top of the screen.
I haven't bothered to go beyond the preview, as that isn't even working. The list is the only one I have wired up right now, and I'm just at the stage of simply printing the meeting names to make sure I have the basic layout right.
Which I obviously don't.
Any ideas?
All right. I figured out what the issue was.
I was trying to do this using UIKit routines, which assume a fairly high-level drawing context. The drawText(in: CGRect) thing was my lightbulb.
I need to do everything using lower-level, context-based drawing, and leave UIKit out of it.
Here's how I implement the drawMeeting routine now (I have changed what I draw to display more relevant information). I'm still working on it, and it will get larger:
func drawMeeting(at meetingIndex: Int, in contentRect: CGRect) {
let myMeetingObject = self.meetings[meetingIndex]
var remainingRect = contentRect
if (1 < self.meetings.count) && (0 == meetingIndex % 2) {
if let drawingContext = UIGraphicsGetCurrentContext() {
drawingContext.setFillColor(UIColor.black.withAlphaComponent(0.075).cgColor)
drawingContext.fill(contentRect)
}
}
var attributes: [NSAttributedStringKey : Any] = [:]
attributes[NSAttributedStringKey.font] = UIFont.italicSystemFont(ofSize: 12)
attributes[NSAttributedStringKey.backgroundColor] = UIColor.clear
attributes[NSAttributedStringKey.foregroundColor] = UIColor.black
let descriptionString = NSAttributedString(string: myMeetingObject.description, attributes: attributes)
let descriptionSize = contentRect.size
var stringRect = descriptionString.boundingRect(with: descriptionSize, options: [NSStringDrawingOptions.usesLineFragmentOrigin,NSStringDrawingOptions.usesFontLeading], context: nil)
stringRect.origin = contentRect.origin
descriptionString.draw(at: stringRect.origin)
remainingRect.origin.y -= stringRect.size.height
}

limit input options in init() in swift

I'm making a simple Dice class in swift.
I would like the Dice initializer to be called with the desired amount of eyes/sides the dice should have. I have two variables to set a min and max of number of sides you should be able to give the dice upon init...
However, I'm not quite sure of how to make the init fail if the dice is being initialized with a number outside of this range, when I can't make sue of try/catch in swift.
My code is as follows:
class Dice : SKSpriteNode {
let sides : UInt32
var score : Int
init(sides : Int){
let min_sides = 2
let max_sides = 6
self.sides = UInt32(sides)
self.score = 1
let imageName = "1.png"
let cardTexture = SKTexture(imageNamed: imageName)
super.init(texture: cardTexture, color: nil, size: CGSize(width: 100, height: 100))
userInteractionEnabled = true
}
Use a failable initializer instead. From that you can return a nil value if the condition doesnt satisfied
init?(sides : Int){
if sides > max_sides{
return nil
}
}

Resources