UISlider moves jerky and/or redrawing is slow - ios

I've been writing a simple fahrenheit/celcius converter style app. It works fine in the simulator but when I test the app on my iPhone 4, it is very jerky and slow to update as I move the slider back and forth.
My main view controller looks like this (removed some junk):
#import "MLViewController.h"
#import "MLGradeModel.h"
#import "MLGrade.h"
#interface MLViewController ()
#property (nonatomic, strong) MLGradeModel *gradeModel;
#end
#implementation MLViewController
#synthesize displayLeft = _displayLeft;
#synthesize displayRight = _displayRight;
#synthesize gradeModel = _gradeModel;
#synthesize buttonLeft = _buttonLeft;
#synthesize buttonRight = _buttonRight;
- (IBAction)dragEnter:(UISlider*)sender {
[self sliderUpdate: sender];
}
- (IBAction)sliderInput:(UISlider*)sender {
[self sliderUpdate:sender];
}
- (IBAction) sliderValueChanged:(UISlider *)sender {
[self sliderUpdate:sender];
}
-(IBAction)sliderUpdate:(UISlider*)sender
{
UILabel *myDisplayLeft = self.displayLeft;
UILabel *myDisplayRight = self.displayRight;
float sliderValue = [sender value];
int pos = sliderValue*1000/CONVERSION_SCALE;
NSString *strLeft = [self.gradeModel readGradeFromLeftAtPos:pos];
NSString *strRight = [self.gradeModel readGradeFromRightAtPos:pos];
[myDisplayLeft setText:strLeft];
[myDisplayRight setText:strRight];
// try to redraw, maybe less jerky?
[self.view setNeedsDisplay];
}
-(int) getSliderValue
{
float initialValue = [self.sliderInput value];
return initialValue * 100;
}
- (void)viewDidLoad
{
[super viewDidLoad];
MLGradeModel *model = [MLGradeModel sharedMLGradeModel];
self.GradeModel = model;
}
#end
gradeModel is an NSMutableArray of NSMutableArrays, who contain NSString values. As the slider is dragged, the corresponding position in the arrays should be read. Whose values should then be set to the UILabels.
I thought it should be the most simple thing. Outlets and actions for the UISlider have been dragged in the storyboard.
Edit: Also, when I run the app on the phone, the logs show that the slider input is taken, but the window is not updated at speed "so to say". For example, then I move the slider form left to right the event shows up in the log but the labels are redrawn with 0.5 s delay.
Edit: [self.view setNeedsDisplay]; was added as a test to force a redraw. The program works equally bad/slow when that line is commented away.
Edit: It works equally bad when I change sliderUpdate to:
-(IBAction)sliderUpdate:(UISlider*)sender
{
float sliderValue = [sender value];
int pos = sliderValue*10;
NSString *strFromInt = [NSString stringWithFormat:#"%d",pos];
[self.displayLeft setText:strFromInt];
[self.displayRight setText:strFromInt];
}
-Have I not hooked up all events from the UISlider? I have only set valueChanged to to my viewController's sliderInput (which calls sliderUpdate).

I have this same delay issue with UISlider on an iPhone 4 with my app now i have started targeting iOS 7
On iOS 6 my sliders worked perfectly on an iPhone 4.
The same app works smoothly on a 4s and an ipad mini that i have here
EDIT
What I've just discovered is that in my app there was big difference in the UISlider performance on iPhone 4 under iOS 7 when I built for debug and built for release.
I had a bunch of logging going on in the slider - using DLog instead of NSLog - DLog is a macro that expands to an NSlog on running in debug mode and a no-op in release mode. so it appears that the logging was causing the lagging.
Check to see if you have logging going on in there and either comment them out or, if you are using Dlog, try changing the scheme to release and see if this solves your issue and see if that makes a difference,
To change to release look in Xcode menu Product-Scheme-Edit Scheme)
Made the world of difference to me.
Bit of a relief that !

Related

Predictable stutter bug with iOS, SpriteKit and iPad 2

I am developing a game for iOS with Obj-C and Apple's SpriteKit framework. My primary testing device is an iPad2 (model #MC773X/A running iOS8.1). My game runs smoothly on this device except for a single "stutter" around 60-120s in. At this time the app lags for around 150-300ms / 10-20 frames.
I have tested the same game app on an iPhone6+ (model #MGAJ2X/A running iOS8.4) which doesn't seem to have this problem.
I originally thought it had something to do with my game apps resource management, and then later maybe the debugging link itself... so I created the following testing app. It is simply a blank screen, that logs dt's above 20ms. It has an option to log to the screen (using SKLabelNodes) for testing without a debugging link.
This simple testing app shows the same stutter on the iPad2 without fail at around the 60s mark (built in debug/release, with/without debugging). The iPhone 6+ doesn't show the stuttering behaviour.
Starting with a new SpriteKit game template for iOS (I am using Xcode 6.4 but I saw the same bug with an earlier version - Xcode 6.something):
In GameViewController.m:
//scene.scaleMode = SKSceneScaleModeAspectFill;
scene.scaleMode = SKSceneScaleModeResizeFill;
Replace GameScene.m with:
#import "GameScene.h"
#define LOG_TO_SCREEN
static int dtLogCounter = 1;
#ifdef LOG_TO_SCREEN
static const int dtLogLabelNum = 10; //5;
#endif
#interface GameScene ()
#property (nonatomic) CFTimeInterval prevTime;
#property (nonatomic) CFTimeInterval startTime;
#ifdef LOG_TO_SCREEN
#property (strong, nonatomic) SKLabelNode *timeLabel;
#property (strong, nonatomic) NSArray *dtLogLabels;
#endif
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view
{
#ifdef LOG_TO_SCREEN
CGFloat screenWidth = view.bounds.size.width;
_timeLabel = [SKLabelNode labelNodeWithFontNamed:#"Courier"];
_timeLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
_timeLabel.fontSize = 16.0;
_timeLabel.position = CGPointMake(screenWidth - 7.0, 28.0);
[self addChild:_timeLabel];
NSMutableArray *dtLogLabels = [NSMutableArray arrayWithCapacity:dtLogLabelNum];
for (int i = 0; i < dtLogLabelNum; i++)
{
SKLabelNode *dtLogLabel = [SKLabelNode labelNodeWithFontNamed:#"Courier"];
dtLogLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
dtLogLabel.fontSize = 16.0;
dtLogLabel.position = CGPointMake(screenWidth - 7.0, 100.0 + 25*i);
[self addChild:dtLogLabel];
[dtLogLabels addObject:dtLogLabel];
}
_dtLogLabels = [NSArray arrayWithArray:dtLogLabels];
#endif
_prevTime = _startTime = CACurrentMediaTime();
}
-(void)update:(CFTimeInterval)currentTime
{
CFTimeInterval dt = currentTime - _prevTime;
_prevTime = currentTime;
CFTimeInterval gameTime = currentTime - _startTime;
#ifdef LOG_TO_SCREEN
_timeLabel.text = [NSString stringWithFormat:#"t:%.1f(c)", gameTime];
#endif
if (dt > 20e-3)
{
NSString *logStr = [NSString stringWithFormat:#"#%d:%.1f:%.0f ", dtLogCounter++, gameTime, dt*1000];
#ifdef LOG_TO_SCREEN
for (int i = dtLogLabelNum-1; i > 0; i--)
{
SKLabelNode *dtLogLabel = [_dtLogLabels objectAtIndex:i];
SKLabelNode *prevLabel = [_dtLogLabels objectAtIndex:i-1];
dtLogLabel.text = prevLabel.text;
}
SKLabelNode *firstDtLogLabel = [_dtLogLabels objectAtIndex:0];
firstDtLogLabel.text = logStr;
#else
NSLog(#"%#", logStr);
#endif
}
}
#end
I am getting comfortable with using the time profiler instrument but in this case I don't think it is particularly helpful. It shows no activity for the duration of the stutter.
(image1)
(Unfortunately I'm a brand new poster so can't inline images)
It seems like it is a system process happening at this time which is causing the app to stutter. The activity monitor instrument shows a peak at this time but I don't really know how to interpret the data / nothing obvious stands out.
(image2)
The system usage instrument shows no activity in the I/O activity track.
The file activity instrument doesn't seem to be available.
My question is: how should I approach debugging this issue? Which instrument could I use, and how?
Confirmation that others see this issue on this/other hardware would be appreciated as would suggestions to it's cause/resolution.
Thanks
If you haven't already, I suggest you read Apple's Analyzing CPU Usage in Your App. If that does not yield any positive results for you, peppering your code with NSLog statements can be another way to try to pinpoint the code section involved with this issue.
Do keep in mind that you are probably not seeing the stutter in the iPhone 6+ due to its faster CPU. The iPad 2 is somewhat dated and runs much slower. This itself is a small clue to your issue.

Nil label with spritebuilder, can't figure out how to fix it

I am making an iOS game with sprite builder. And I designed it around a board game style. I want the user to press the play button that triggers the play method. It then generates a random number that should be displayed on a label. The label and button are on the gameplay scene. The game play scene is a subclass of CCNode. The code is on the Gameplay class that is a subclass of CCNode. I have found out that the label is nil. How do I make it not nil? The code connection for my label is doc root var assigned to _randNumLabel. My gameplay code connection is assigned Gameplay. This is my log after I open the scene and click the button:
2014-06-09 17:20:12.565 Sunk[6037:60b] CCBReader: Couldn't find member variable: _randNumLabel
2014-06-09 17:20:12.567 Sunk[6037:60b] CCBReader: Couldn't find member variable: _affectLabel
2014-06-09 17:20:19.513 Sunk[6037:60b] Nil`
Ignore the _affectLabel as it will get fixed if _randNumLabel gets fixed.
#import "Gameplay.h"
#implementation Gameplay
CCLabelTTF *_randNumLabel;
- (void)play {
if (_randNumLabel == nil)
{
CCLOG(#"Nil");
}
if (_randNumLabel !=nil)
{
CCLOG(#"play button pressed!");
int max = 6;
int randNumber = (arc4random() % max) + 1; // Generates a number between 1-6.
CCLOG(#"Random Number %d", randNumber);
_randNumLabel.string = [NSString stringWithFormat:#"Number: %d", randNumber];
}
}
- (void)update:(CCTime)delta {
}
#end
You need to declare your instance variables using curly brackets, i.e.:
#implementation Gameplay
{
CCLabelTTF *_randNumLabel;
}
- (void)play {
// rest of your code
...
As a personal preference I would use a private property instead of an instance variable, e.g.
#interface Gameplay ()
#property (nonatomic, strong) CCLabelTTF *randNumLabel;
#end
#implementation Gameplay
- (void)play {
// rest of your code
...

ARC semantic issue "multiple methods named 'setRotation' " while archiving only

My project in cocos2dv3 is throwing ARC Sematic Issue
Multiple methods named 'setRotation:' found with mismatched result, parameter type or attributes
while archiving(release mode). It runs fine while deploying to simulator/device (debug mode).
In release mode compiler gets confused between the implementation of rotation in UIRotationGestureRecognizer and CCNode.
When I got the error in CCBAnimationManager.m , I typecasted the object calling the selector setRotation to (CCNode*) but then the error crept up in CCActionInterval. I'm hoping there is a better solution than typecasting everywhere in cocos2d library.
What am i doing wrong?
Thankyou for your time.
EDIT
#interface CCAction : NSObject <NSCopying> {
id __unsafe_unretained _originalTarget;
id __unsafe_unretained _target;
NSInteger _tag;
}
#property (nonatomic,readonly,unsafe_unretained) id target;
#property (nonatomic,readonly,unsafe_unretained) id originalTarget;
#property (nonatomic,readwrite,assign) NSInteger tag;
in
CCAction.m
#synthesize tag = _tag, target = _target, originalTarget = _originalTarget;
-(void) startWithTarget:(id)aTarget
{
_originalTarget = _target = aTarget;
}
-(void) startWithTarget:(id)aTarget
{
_originalTarget = _target = aTarget;
}
Class Hierarchy
#interface CCActionFiniteTime : CCAction <NSCopying>
#interface CCActionInterval: CCActionFiniteTime <NSCopying>
#interface CCBRotateTo : CCActionInterval <NSCopying>
CCBRotateTo.m {
-(void) startWithTarget:(CCNode *)aTarget
{
[super startWithTarget:aTarget];
startAngle_ = [self.target rotation];
diffAngle_ = dstAngle_ - startAngle_;
}
-(void) update: (CCTime) t
{
[self.target setRotation: startAngle_ + diffAngle_ * t];
}
}
This problem gave me a big headache. Though I've upgraded cocos2d to v2.2 version for my old project (too complex to update to v3), I still got this warning. And any animation I created use rotation in the SpriteBuilder does act oddly, as I described here:
Rotation animation issue on iPhone5S with cocos2d 2.0
Finally I used type casting to solve it as following in CCBAnimationManager.m
#implementation CCBRotateTo
-(void)startWithTarget:(CCNode *)aTarget
{
[super startWithTarget:aTarget];
starAngle_ = [(CCNode *)self.target rotation];
diffAngle_ = dstAngle_ - startAngle_;
}
-(void)update:(ccTime)t
{
[(CCNode *)self.target setRotation: startAngle_ + diffAngle_ * t];
}
With this change, now I can support arm64 too.
update your cocos2dv3 to latest (RC4 for now).
I was using Xcode 5.0 and cocos2dv3 RC1 with no problem.
But updating Xcode to 5.1 I had this problem.
So I updated the cocos2dv3 to RC4 and now it's working fine.
You can find cocos 2d latest versions from here.

iOS mixing Spritekit and UIKit

I'm in the process of writing a fairly simple Math's puzzle game for one of my children.
I used the standard XCode SpriteKit Game template which creates an SKView with an SKScene and ViewController.m and MyScene.m files.
I then added a simple UIView to act as a container for a NumberPad 0-9 with UIButtons.
I'm tempted to target MyScene.m as the target for the IBActions as it will use the changes in state from the button presses.
However I'm wondering which class is a better target for the IBActions the ViewController or MyScene. In particular are there any performance implications to either choice.
My main concern is some articles I've seen about issues people have encountered when mixing SpriteKit and UIKit.
For your purposes the performance implications are completely negligible. However from a code point of view I would probably target the viewcontroller, in case you want to swap out the scene but reuse your numberpad. It sounds like a likely development in the future for a math type game.
I took Theis' advice and so my view controller, which creates the scene, contains the following code.
ViewController.m
- (IBAction)numberPressed:(id)sender {
UIButton *pressedButton = (UIButton*) sender;
NSString *button = pressedButton.currentTitle;
[scene.keysPressed addObject:button];
}
- (IBAction)clearPressed:(id)sender {
UIButton *pressedButton = (UIButton*) sender;
NSString *button = pressedButton.currentTitle;
[scene.keysPressed addObject:button];
}
And then in my scene code I have declared the keysPressed property, being somewhat paranoid I've made it atomic in case the view controller and scene ever run in different threads.
MyScene.h
#property (strong, atomic) NSMutableArray *keysPressed;
And then in the scenes update method I just check the mutable array I'm using as a stack to see anything has been added and get its value and delete it.
MyScene.m
-(void)update:(CFTimeInterval)currentTime {
...
NSString *keyNum = nil;
if([_keysPressed count] > 0) {
keyNum = [_keysPressed firstObject];
[_keysPressed removeObjectAtIndex:0];
}
So far everything is behaving itself.

Overriding "text" in UILabel does not work in iOS 6

My class "TypographicNumberLabel" is a subclass of UILabel. This class overrides the "text" setters and getters of UILabel with the purpose to produce nicely rendered numbers in a table. For instance, it can add some extra white space for right alignment, unary plus signs, append units, etc.
My problem is that this class has worked perfectly fine up to iOS 5.1, but in iOS 6, it has stopped working: It is now rendering exactly as the standard UILabel (but when its properties are accessed from code, they are still giving correct results).
Since this class is used in a huge mass of legacy code, I would really like to repair my original code instead of rewriting it using completely new methods. So, please focus your answers on explaining how to override "-text" and "-setText:" for UILabel in iOS 6.
This is (a simplified version of) my code:
#interface TypographicNumberLabel : UILabel {
NSString *numberText;
}
// PROPERTIES
// "text" will be used to set and retrieve the number string in its original version.
// integerValue, doubleValue, etc. will work as expected on the string.
// The property "text" is declared in UILabel, but overridden here!
// "typographicText" will be used to retrieve the string exactly as it is rendered in the view.
// integerValue, doubleValue, etc. WILL NOT WORK on this string.
#property (nonatomic, readonly) NSString* typographicText;
#end
#implementation TypographicNumberLabel
- (void) renderTypographicText
{
NSString *renderedString = nil;
if (numberText)
{
// Simplified example!
// (Actual code is much longer.)
NSString *fillCharacter = #"\u2007"; // = "Figure space" character
renderedString = [fillCharacter stringByAppendingString: numberText];
}
// Save the typographic version of the string in the "text" property of the superclass (UILabel)
// (Can be retreived by the user through the "typographicText" property.)
super.text = renderedString;
}
#pragma mark - Overridden UILabel accessor methods
- (NSString *) text
{
return numberText;
}
- (void) setText:(NSString *) newText
{
if (numberText != newText)
{
NSString *oldText = numberText;
numberText = [newText copy];
[oldText release];
}
[self renderTypographicText];
}
#pragma mark - TypographicNumberLabel accessor methods
- (NSString *) typographicText
{
return super.text;
}
#end
Example of use (aLabel is loaded from .xib file):
#property(nonatomic, retain) IBOutlet TypographicNumberLabel *aLabel;
self.aLabel.text = #"12";
int interpretedNumber = [self.aLabel.text intValue];
This type of code works perfectly fine in both iOS 5.1 and in iOS 6, but the rendering on screen is wrong in iOS 6! There, TypographicNumberLabel works just like a UILabel. The "figure space" character will not be added.
The issue is at
- (NSString *) text
{
return numberText;
}
You can see the method ([self text]) is called internally, so it's better to return the text you want to be shown, otherwise you can easily ruin internal control logic:
- (NSString *) text
{
return [super text];
}
After having submitted my question, I found a solution myself. Perhaps not the definite solution, but at least a useful workaround. Apparently, the rendering logic of UILabel has changed when attributedText was introduced in iOS 6. I found that setting the attributedText property instead of super.text will work.
To be more specific:
The following line in renderTypographicText
super.text = renderedString;
should be replaced with
if (renderedString && [UILabel instancesRespondToSelector: #selector(setAttributedText:)])
super.attributedText = [[[NSAttributedString alloc] initWithString: renderedString] autorelease];
else
super.text = renderedString;
then the rendering works fine again!
This is a bit "hackish", I admit, but it saved me from rewriting a huge amount of legacy code.

Resources