This question already has an answer here:
How to make marquee UILabel / UITextField / NSTextField
(1 answer)
Closed 9 years ago.
I am writing an app using a cocoa control that animates text,
https://www.cocoacontrols.com/controls/marqueelabel
It uses CoreText and QuartzCore.
It's a pretty great control, but i'm having trouble.
When instantiated the animated labels that I create using the control start to scroll immediately, however I'm finding that when I create an animated label using NSTimer it is not animating.
I am using
- (void)viewDidLoad
{
[super viewDidLoad];
....
[self.view addSubview:continuousLabel1];
[NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(updatePrice)
userInfo:nil repeats:YES];
}
to call a method that creates the animated label, when I call the method directly the labels create and animate, however when called using the above scheduled timer, new labels created only animate on the first call, not on the timers repeat calls to the method.
I have checked that my method is being call on the main thread/ main runloop, any ideas?
For clarity the work flow is:
Timer calls method --> Label 1 is created and starts to Animate.
5 Seconds Late...
Timer calls method again --> Label 2 is created However is does not begin to animate as expected.
EDIT:
- (void)updatePrice
{
MarqueeLabel *continuousLabel2 = [[MarqueeLabel alloc] initWithFrame:CGRectMake(10, 230, self.view.frame.size.width-20, 20) rate:100.0f andFadeLength:10.0f];
continuousLabel2.tag = 101;
continuousLabel2.marqueeType = MLContinuous;
continuousLabel2.animationCurve = UIViewAnimationOptionCurveLinear;
continuousLabel2.continuousMarqueeExtraBuffer = 50.0f;
continuousLabel2.numberOfLines = 1;
continuousLabel2.opaque = NO;
continuousLabel2.enabled = YES;
continuousLabel2.shadowOffset = CGSizeMake(0.0, -1.0);
continuousLabel2.textAlignment = NSTextAlignmentLeft;
continuousLabel2.textColor = [UIColor colorWithRed:0.234 green:0.234 blue:0.234 alpha:1.000];
continuousLabel2.backgroundColor = [UIColor clearColor];
continuousLabel2.font = [UIFont fontWithName:#"Helvetica-Bold" size:17.000];
continuousLabel2.text = #"This is another long label that doesn't scroll continuously with a custom space between labels! You can also tap it to pause and unpause it!";
[self.view addSubview:continuousLabel2];
if( [NSThread isMainThread])
{
NSLog(#"In Main Thread");
}
if ([NSRunLoop currentRunLoop] == [NSRunLoop mainRunLoop]) {
NSLog(#"In Main Loop");
}
}
The problem is that you are calling updatePrice every 5 seconds and in this method you are creating a new label, that that is stacking up, and notice that it is going darker and darker every 5 seconds.
And if you check the label is scrolling.
Solution:
If you want multiple instances of the label change its coordinates.
If you want single instance then compare check and create it just once.
You should read the section Important Animation Note from the README file.
My guess is that you will need to call controllerLabelsShouldAnimate: passing your current view controller to animate those labels added after the view controller has already been shown.
Related
When i push the button I would like the first image to show in the UIImageView, stay for a short period of time and then the next image to show. Only the second image shows after a period of time. The first image never comes on.
// TestProjectViewController.m
// Created by Jack Handy on 3/8/12.
#import "TestProjectViewController.h"
#implementation TestProjectViewController
#synthesize View1= _view1;
#synthesize yellowColor = _yellowColor;
#synthesize greenColor = _greenColor;
- (IBAction)button:(id)sender {
_greenColor = [UIImage imageNamed: #"green.png"];
_view1.image = _greenColor;
[NSThread sleepForTimeInterval:2];
_yellowColor = [UIImage imageNamed: #"yellow.png"];
_view1.image = _yellowColor;
}
#end
U can try and place the
_yellowColor = [UIImage imageNamed: #"yellow.png"];
_view1.image = _yellowColor;
and instead of the
[NSThread sleepForTimeInterval:2];
call this one
[self performSelector:#selector(changeColor) withObject:nil afterDelay:2];
The problem here is that you are replacing the image before the OS gets a chance to draw. Since all of these three operations: change the image, wait 2 seconds, change the image again) occur before your button action returns, you are preventing the Main thread from executing and thus the screen from refreshing. So, what's happening is that after 2 seconds, the screen draws with the image you most recently put in its place.
You need to have the waiting happen separately. There are three typical ways to do this, each of which have their benefits:
- send yourself a delayed message using -performSelector:withObject:afterDelay:
- spawn another thread or use a dispatch queue to run a thread in the background for the sleep and then send the message to the main thread from there
- or, use a timer.
My suggestion would be to use the timer, because it is easily cancelable if you need to do something like move to another screen.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target: self selector: #selector(updateColor:) userInfo: nil repeats: NO];
// store the timer somewhere, so that you can cancel it with
// [timer invalidate];
// later as necessary
and then later:
-(void)updateColor:(NSTimer*)timer
{
_yellowColor = [UIImage imageNamed: #"yellow.png"];
_view1.image = _yellowColor;
}
If you want the colors to alternate, you can pass YES for the repeats: value in the creation code and then change -updateColor: to alternate... or move to the next color.
I've made a custom zero-image, Core Graphics drawn UIButton based on Ray Wenderlich's Tutorial (the only modifications are to CoolButton's drawRect: method, altered drawing code), and it works great most of the time. However, sometimes when I click it for a short period of time, it stays in a depressed state and doesn't return to normal.
From here, the only way to get it back to a normal state is via a long press. Simply clicking means it stays depressed.
Another thing to note is that I've hooked Touch Up Inside up to a chain of a few long methods - I don't think it would take more than 0.1 seconds to complete. I've even used dispatch_async in the #selector that is hooked up to Touch Up Inside, so there shouldn't be a delay in the UI updating, I think.
I've put an NSLog in the drawRect: which fires 3 times per button press usually, and it varies what UIControlState the button is in for each press:
For some short presses, it goes Highlighted, Highlighted, Normal
for longer presses, it's Highlighted, Normal, Normal
However, for very short presses, it only fires twice, Highlighted -> Highlighted.
When it's a long press to get it back to Normal, it goes H, N, N.
This has been puzzling me for a while, and I haven't been able to work out why short presses only fire drawRect: twice, or why touchesEnded: doesn't seem to call drawRect:. Perhaps touchesEnded: isn't firing?
I really hope someone can help.
If you really want to generate the button images at runtime, generate them when the button is loaded. Then add them for different states using
[button setImage:btnImage forState:UIControlStateNormal];
You can always turn a view into an image using the following: http://pastie.org/244916
Really though, I'd recommend just making images beforehand. If you don't want to get photoshop, there's plenty of alternatives. The upcoming pixelmator update looks pretty suave, ands it's ridiculously cheap!
Well, well, that was easy. 0.15 second delay works, 0.1 doesn't.
As other said, you can simply generate an image and use that as background (if the image is static). No need photoshop, you may simply generate your image once and then take a snapshot, then cut the image out (with Anteprima) and save it as a png file :)
However, since you said the button is connected to some long methods, this may be why the button stay pressed: if you are not calling those methods in background, then the button will stay pressed since all the task are ended. Try (for a test) to connect the button with a single simple method (say NSLog something) and check if it stay pressed. If not, I suggest to detach your methods in background.
I too ran into a similar problem with a UIButton appearing to stick one state when I customize the drawRect method. My hunch is that the state of the button is changing more than once before drawRect is called. So I just added a custom property that would only be set by methods called during touchDown and touchUpInside events. I had to dispatch threads to do this because of other operations that were holding up the main thread, causing a delay in the redrawHighlighted method.
It may be a little messy, but here's what worked for me:
// CustomButton.h
#interface CustomButton : UIButton
#property (atomic) BOOL drawHighlighted;
// CustomButton.m
#implementation CustomButton
#synthesize drawHighlighted = _drawHighlighted;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
_drawHighlighted = NO;
[self addTarget:self action:#selector(redrawHighlighted) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(redrawNormal) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:#selector(redrawNormal) forControlEvents:UIControlEventTouchDragExit];
}
return self;
}
- (void)drawRect:(CGRect)frame
{
// draw button here
}
- (void)redrawNormal
{
[NSThread detachNewThreadSelector:#selector(redrawRectForNormal) toTarget:self withObject:nil];
}
- (void)redrawHighlighted
{
[NSThread detachNewThreadSelector:#selector(redrawRectForHighlighted) toTarget:self withObject:nil];
}
- (void)redrawRectForNormal
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
_drawHighlighted = NO;
[self setNeedsDisplay];
[pool release];
}
- (void)redrawRectForHighlighted
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
_drawHighlighted = YES;
[self setNeedsDisplay];
[pool release];
}
Another cheap but effective alternative I've found is to create a drawRect: that doesn't do any highlighting, and simply alter the button subclass's alpha property when the user interacts with the button.
For example:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.alpha = 0.3f;
return [super beginTrackingWithTouch:touch withEvent:event];
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.alpha = 1.0f;
[super endTrackingWithTouch:touch withEvent:event];
}
- (void)cancelTrackingWithEvent:(UIEvent *)event
{
self.alpha = 0.3f;
[super cancelTrackingWithEvent:event];
}
- (void)drawRect:(CGRect)rect
{
// Your drawing code here sans highlighting
}
I'm using animation method of for implementing slide show in UIimageView as:
mainSlideShowImageView.animationImages=images;
mainSlideShowImageView.animationDuration = 75.00;
mainSlideShowImageView.animationRepeatCount = 0; //infinite
[mainSlideShowImageView startAnimating];
Now, what I want is to know which image is currently on image view screen.
How can I do so?
If it is not possible please tell that too.
Edit: 'images' here is array of UIImage.
AFAIK, there is no method which returns imageview's current image , however I have created a workaround for this....
array= [NSArray arrayWithObjects:[UIImage imageNamed:#"1.jpg"],[UIImage imageNamed:#"2.jpg"],[UIImage imageNamed:#"3.jpg"],[UIImage imageNamed:#"4.jpg"],[UIImage imageNamed:#"5.jpg"],nil];
mainSlideShowImageView.animationImages= array;
mainSlideShowImageView.animationDuration=15.0;
i=0;
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(increase) userInfo:nil repeats:YES];
[mainSlideShowImageView startAnimating];
-(void)increase
{
i++;
if (i>4) {
i=0;
}
}
-(void)getimage
{
UIImage* im = [array objectAtIndex:i];
// im is the current image of imageview
}
I have taken 5 images here and animation duration of 15 , that means image will change at every 3 seconds, so I have kept timer to fire at every 3 seconds ... and since array contains 5 images ... so if 'i' goes beyond 4 I have put it back to 0 in method increase
What I am trying to achieve is showing a view during a couple of seconds without user intervention. Is the same effect as the ringer volume view that appears when pressing the volume controls on iphone:
I have a scroll view with an image, taping in the image, sound begin to play, another tap and it pauses. I would like to implement the above effect just to inform of the action (showing a play/pause images).
I hope that I have explained the problem perfectly.
Many thanks for your help.
Regards
Javi
Assume you have some class inherited from UIViewController. You can use the code below:
const int myViewTag = 10001;
const int myInterval = 1; // define the time you want your view to be visible
- (void)someAction {
//this could be your `IBAction` implementation
[self showMyView];
[NSTimer scheduledTimerWithTimeInterval:myInterval
target:self
selector:#selector(hideMyView)
userInfo:nil
repeats:NO];
}
- (void) showMyView {
//you can also use here a view that was declared as instance var
UIView *myView = [[[UIView alloc] initWithFrame:CGRectMake(100, 100, 120, 120)] autorelease];
myView.tag = myViewTag;
[self.view addSubview:myView];
}
- (void) hideMyView {
//this is a selector that called automatically after time interval finished
[[self.view viewWithTag:myViewTag] removeFromSuperview];
}
You can also add some animations here but this is another question :)
I tried to create a SplashView which display the Default.png in the background and a UIProgressBar in front. But the splash screen is not being updated...
Inside my view controller I load first the splash view with a parameter how many steps my initialisation has and then I start a second thread via NSTimer and after each initialisation step I tell the SplashView to display the new progress value.
All looks good in theory, but when running this app the progress bar is not being updated (the method of the splash screen receives the values, I can see it in the logs). I also tried to add usleep(10000); in between to give the view updates a bit time and also instead of using the progress bar I drew directly on the view and called [self setNeedsDisplay]; but all didn't work :/
What am I doing wrong?
Thanks for your help!
Tom
Here is some code:
SPLASHSCREEN:
- (id)initWithFrame:(CGRect)frame withStepCount:(int)stepCount {
if (self = [super initWithFrame:frame]) {
// Initialization code
background = [[UIImageView alloc] initWithFrame: [self bounds]];
[background setImage: [UIImage imageWithContentsOfFile: [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], #"Default.png"]]];
[self addSubview: background];
progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
[progressView setFrame:CGRectMake(60.0f, 222.0f, 200.0f, 20.0f)];
[progressView setProgress: 0.0f];
stepValue = 1.0f / (float)stepCount;
[self addSubview:progressView];
}
return self;
}
- (void)tick {
value += stepValue;
[progressView setProgress: value];
}
VIEWCONTROLLER:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
splashView = [[SplashView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 480.0f) withStepCount:9];
[self setView: splashView];
NSTimer* delayTimer;
delayTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(finishInitialization) userInfo:nil repeats:NO];
}
return self;
}
- (void)finishInitialization {
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// init done... set the right view and release the SplashView
}
As mentioned in another answer, for some finite amount of time, as your app is being launched, Default.png is displayed and you have no control over it. However, if in your AppDelegate, you create a new view that displays the same Default.png, you can create a seamless transition from the original Default.png to a view that you can add a progress bar to.
Now, presumably, you have created a view or similar and you are updating a progress bar every so often in order to give the user some feedback. The challenge here is that your view is only drawn when it gets called to do a drawRect. If, however, you go from AppDelegate to some initialization code to a viewcontroller's viewDidLoad, without the run loop getting a chance to figure out which views need to have drawRect called on, then your view will never display its status bar.
Therefore in order to accomplish what you want, you have to either make sure that drawRect gets called, such as by pushing off a lot of your initialization code into other threads or timer tasks, or you can force the drawing by calling drawRect yourself, after setting up contexts and such.
If you go with the background tasks, then make sure your initialization code is thread-safe.
Default.png is just a graphic, a static image shown while the application is launching. If you want to show further progress, you'll have to show everything at the applicationDidLaunch phase. Show your modal "Splash Screen" there first (Create a view controller, add its view as a subview of your main window) and dismiss it when you are done whatever additional loading you needed to do.
Also, you need to do update your progress bar in a seperate thread. Updating your GUI in the same thread where a lot of business is going on is (in my opinion, but I could be wrong) a bad idea.
The main thread is, as far as I know, the only one that can safely do GUI things, and its event loop (that is, the main application thread's) is the one that does the actual displaying after you've called -setNeedsDisplay. Spawn a new thread to do your loading, and update the progress on the main thread.