How to Scroll the text in label automatically - objective-c

I have an array , data of which 'm displaying on scrollview using UILabel. I can scroll right and left. But I want to add one more feature in that which is automatic scrolling of the label text on scrollview. That means even if I do not scroll I want it to move by itself.
My code for scrolling is as below:
- (void)viewDidLoad
{
[super viewDidLoad];
_scrollView.contentInset=UIEdgeInsetsMake(0, 300, 0, 300);
NSMutableArray *items=[[NSMutableArray alloc] initWithObjects:#"A walk to remember",#"The Last song",#"The Notebook",#"Dear John",#"The Longest Ride",nil];
CGFloat y = 10;
self.label = [[UILabel alloc]initWithFrame:CGRectMake(0,y,800, 25)];
for(int i=0;i<[items count];i++){
y+=20;
NSString* lab=[items objectAtIndex:i];
_label.backgroundColor=[UIColor clearColor];
lab=[items componentsJoinedByString:#" | "];
NSLog(#"label:%#",lab);
[self.label setLineBreakMode:NSLineBreakByWordWrapping];
[self.label setText:lab];
}
NSTimer *timer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(timer)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//[_scrollView scrollRectToVisible:NSMakeRange(0,0,0.0) animated:YES];
[_scrollView addSubview:_label];
[UIView commitAnimations];
_scrollView.contentSize=CGSizeMake(_scrollView.frame.size.width*[items count], _scrollView.frame.size.height);
}
timer code:
-(void)timer {
[self.label setText:[NSString stringWithFormat:#" %#", self.label.text]];
}
Please help me how could I make the label text move horizontally. Thanks for any help

That's a fairly horrible way to implement scrolling if I may so so! You're scrolling by inserting incremental spaces to the left of the string. There is a much easier way to do this!
Keep your code that adds the UILabels to your scroll view
Get rid of the NSTimer stuff
Add a basic UIView animation to animate the scrollview's content offset.
Something like this:
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
_scrollview.contentOffset = CGPointMake(-50.0f, 0.0f);
} completion:nil];
This will slide the whole scrollview left by 50 points over 0.3secs. You can play around with the settings.
btw, typically you'd create the UILabels in viewDidLoad (as you have it) but put the animation in viewWillAppear or viewDidAppear
Have had another look at your code. I thought you were adding multiple UILabels to your uiscrollview but actually you're adding just one with a long text string.
You can simplify your code to set up the label quite a bit, then implement the scrolling animation per the below.
NSMutableArray *items=[[NSMutableArray alloc] initWithObjects:#"A walk to remember",#"The Last song",#"The Notebook",#"Dear John",#"The Longest Ride",nil];
CGFloat y = 10;
self.label = [[UILabel alloc]initWithFrame:CGRectMake(0,y,800, 25)];
[self.label setLineBreakMode:NSLineBreakByWordWrapping];
[self.label setBackgroundColor: [UIColor clearColor]];
[self.label setText: [items componentsJoinedByString:#" | "]];
EDIT
To your question about how to show the whole line scrolling and then repeating, there are a couple of steps here.
Make the UILabel wide enough to take all the text on a single line - i.e. increase the width so that it's big enough to fit all the text. You can do this by trial and error or use (CGSize)sizeThatFits:(CGSize)size
to get the size. You call CGSize size = self.label sizeThatFits:CGSizeMake(CGFloatMax, self.label.frame.size.height) to get the size then apply this to the label's frame:
CGRect frame = self.label.frame;
frame.size.width = size.width; //calculated above with sizeThatFits
self.label.frame = frame; //update the frame
Now scroll the scrollview by the full width. So the animateWithDuration code would become:
_scrollview.contentOffset = CGPointMake(-size.width, 0.0f);
The final step is how to make it restart once it's got to the end. Not sure what effect you want here? Should it jump back to the start? (if so set the contentOffset x to zero), or continue scrolling? (if so, use a trick like adding a second UILabel at the end of the first.
BTW, if what you're after is a simple scrolling text like a banner on a webpage, then I suggest you don't bother with the scrollview at all. Instead add the UILabel to self.view and then animate the UILabel's frame origin directly.

Related

how to animate both the frame of an UIView and the frame of one of its sublayers?

What I have:
I have a UIView (named pView) which has as sublayer a CAGradientLayer. Practically is this:
ViewController -> View ->pView -> CAGradientLayer
This is the code that creates all this:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITestView * pView = nil;
UIColor * scolor = nil, *ecolor = nil;
self.view.backgroundColor = [UIColor yellowColor];
pView = [[UITestView alloc] initWithFrame: CGRectMake(self.view.frame.origin.x, 100.0, self.view.frame.size.width, 100.0)];
scolor = [UIColor colorWithRed:(14/255.0) green: (238/255.0) blue:(123/255.0) alpha:1];
ecolor = [UIColor colorWithRed:(6/255.0) green: (216/255.0) blue:(69/255.0) alpha:1];
// creating the gradient layer
CAGradientLayer * layer = [[CAGradientLayer alloc] init];
layer.frame = self.bounds;
layer.colors = #[(id)scolor.CGColor,(id)ecolor.CGColor];
[pView.layer insertSublayer:layer atIndex:0];
// creating a tapGestureRecognizer
[pView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapInViews:)]];
[self.view addSubview:pView];
touched = NO;
}
....
#end
What I'm trying to do
Once a tap gesture is detected over pView I want to increase the height of pView by 100.0 but animatedly. If pView was previously touched (that is, its height was already increased by 100.0) I want to decrease the height of pView by 100.0 (that is, returning it to its original size);
What I already know
I know that since I want to change the frame of pView I must change also the frame (or bounds) of the CAGradientLayer attached to pView. Since I want to animate these changes, I want these animations occurs at the same time and have the same duration.
I know that the frame (or bounds) of a layer is only animatable inside an animation block.
What I do:
This is the option I've test:
- (void) handleTapInViews: (nonnull UITapGestureRecognizer *) sender {
pView = sender.view;
if (touched) {
[UIView animateWithDuration:0.4 animations:^{
pView.frame = CGRectMake(pView.frame.origin.x, pView.frame.origin.y, pView.frame.size.width, pView.frame.size.height - 100.0);
pView.layer.sublayers[0].frame = pView.bounds;
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
touched = NO;
}];
}
else {
[UIView animateWithDuration:0.4 animations:^{
pView.frame = CGRectMake(pView.frame.origin.x, pView.frame.origin.y, pView.frame.size.width, pView.frame.size.height + 100.0);
pView.layer.sublayers[0].frame = pView.bounds;
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
touched = YES;
}];
}
}
This option actually works (that is, both the frame of pView and the frame of the layer change animatedly) but they are not synchronised; that is, the changes in the frame of the layer are slightly (but perceptible) more faster than the changes in the frame of pView. This effect is more evident when the height of pView is decreased.
I 've also read about CABasicAnimation and CAAnimationGroup, in order to animate both the bounds and position of the layer. In this case also the animation of the layer is bit faster than the animation of the view (it is perceptible and it is not a matter of seconds in case anyone ask about setting duration or whatever) and also in this case, after the animation the bound of the layer return to its original size which is not the desired effect. I already know this last matter can be fixed assigned the new values to the layer at the end of the animation but I certainty do not know where in my code put that.
Most of what i've read regarding this other option is from these links:
link1
link2
link3
link4
In any case, does anybody please knows how can I fix this?? thanks in advance.
Well I ended setting the backgroundColor of Pview as ecolor = [UIColor colorWithRed:(6/255.0) green: (216/255.0) blue:(69/255.0) alpha:1. This way, the effect I commented about the animation of the layer been faster than the animation of the view is not noticeable now. I think maybe this is not the proper answer but it suit me.

UITapGesture only works after reLoad UIViewController

I have 1 year works with Objective-C and is my first question that I make because I always found the answer in this site, but this situation is completely insane and did not found anything similar.
I have a custom class (called BallClass from UIView). The UIView have an UIImageView inside on it and a UITapGesture to make an action.
So when I create the object in a UIViewController, does not works the tapAction when press in the object, but if I change to other screen and return it, the tapGesture works perfect.
I tried many options, but I cant makes work in the first load.
I work with ARC, Xcode (4.6.3) and OSX (10.8.4) and IB but this objects was created with no IB .
Thanks in advance.
Here is the class BallClass.m
- (void)tapGesture:(UIGestureRecognizer *)tapGesture {
NSLog(#"hola");
}
- (id)initBall:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
// Recibir las variables iniciales para el control del timer
ball = [[Ball alloc] init];
// Agregar la imagen de la pelota en el view
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
[imageView setCenter:CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds]))];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
[imageView setImage:[UIImage imageNamed:#"pelota.png"]];
[imageView setTag:100];
[imageView setNeedsDisplay];
// Add TAP
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
// Tap Properties
[tapGesture setDelaysTouchesBegan:YES];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:(id)self];
[imageView addGestureRecognizer:tapGesture];
[self addSubview:imageView];
[self setNeedsDisplay];
// Propiedades del View
[self setUserInteractionEnabled:YES];
self.backgroundColor = [UIColor clearColor];
[self setTag:100];
// BALL BOUNCE
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)onTimer:(NSTimer *)timerLocal {
// Do something
}
... // more methods
Here is the instantiate:
ballView00 = [[BallView alloc] initBall:CGRectMake(10, 10, 40, 40)];
The UiView show perfect, and the animation works fine, but the UITapGesture just works, as mentioned early, after reLoad the ViewController.
Here is the link to image the Ball and UIView (#kBall). Sorry, about image, but I cant load until I have 10 points :(
This might be an issue with the superview's frame. What view is holding ballView00? Does ballView lie entirely within it's superview's bounds?
Usually when you encounter problem where you're expecting a touch but don't get it it's because the view set to receive the touch is outside one of its parents' bounds.
So if the superview bounds are 100x100 and the ball is at x=125 y=125 it will not receive a touch.
A lot of magic happens when your views get laid out, it's not surprising to sometimes find one with 0x0 dimensions because of some incorrect constraints.
You can easily check the bounds of all your views by giving them a background color.
I found the solution.
The ball make a spin with this:
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: UIViewAnimationOptionCurveEaseIn
animations: ^{
self.transform = CGAffineTransformRotate(self.transform, M_PI / 2);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
}
}
}];
And generate conflict with the main timer. I change the spin action with simple add self.transform = CGAffineTransformRotate(self.transform, M_PI / 2); inside drawRect method in the class.
Apparently, I can't nested two timers, because "disable" the tapGesture, so when I change of the screen and return, the spin was stopped and this help to found this situation.
Thanks for your help.

NSImageView disappears without reason

I let my window autoresize to the visible screen. There is no problem in that code.
But the problem lies here I need to add ImageViews to the window and of course they should also be able to resize.
Code to create the ImageViews and to make window fit to current visible
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
int x,y;
NSInteger rowCounter,gapX,gapY;
rowNumber=5;
rowCounter=0;
gapX=0;
gapY=0;
personNumber=10;
startX=20;
startY=20;
NSRect rect;
NSSize size;
NSScreen *scren=[NSScreen mainScreen];
rect=scren.visibleFrame;
y=rect.size.height;
x=rect.size.width;
NSLog(#"%s%s%d%s%d","screen"," y size=",y," x size=",x);
y=window.frame.size.height;
x=window.frame.size.width;
NSLog(#"%s%s%d%s%d"," window"," y size=",y," x size=",x);
timer1 =[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(resizeWindow1) userInfo:nil repeats:YES];
rahmen=[[NSMutableArray alloc] initWithCapacity:25];
NSPoint p;
y=rect.size.height;
x=rect.size.width;
size.height=y;
size.width=x;
p.x= rect.origin.x;
p.y= rect.origin.y+y;
[window setContentSize:size];
[window setFrameTopLeftPoint:p];
width =x-(20*(personNumber/2));
width=(width/(personNumber/2))-(20/(personNumber/2));
NSLog(#"%s%ld","\n width: ",(long)width);
high= y-(20*rowNumber);
high=high/rowNumber-(20/rowNumber);
NSLog(#"%s%ld","\n high: ",(long)high);
for (indexRahmen=0; indexRahmen<25; ) {
if (indexRahmen>0 && indexRahmen%(personNumber/2)==0) {
rowCounter=rowCounter+1;
gapX=0;
gapY=gapY+1;
}
else if (indexRahmen%(personNumber/2)!=0){
gapX=gapX+1;
}
if (rowCounter==rowNumber) {
rowCounterr=rowNumber-1;
gapY=gapY-1;
}
rect=NSMakeRect(startX+(width*(indexRahmen%(personNumber/2)))+(gapX*20),(startY+(rowCounter*high)+(gapY*20)),width, high);
NSImageView *imageView=[[NSImageView alloc] initWithFrame:rect];
imageView.image = [NSImage imageNamed:#"umrandung.png"];
[rahmen insertObject:imageView atIndex:indexRahmen];
[window1view.self addSubview:imageView];
NSLog(#"%s%ld","\n IndexZahl: ",(long)indexRahmen);
indexRahmen=indexRahmen+1;
}
}
the resizing action called by timer1 is nearly the same except that:
NSImageView *imageView= [rahmen objectAtIndex:indexRahmen];
imageView.frame= rect;
imageView.image = [NSImage imageNamed:#"umrandung.png"];
[rahmen insertObject:imageView atIndex:indexRahmen];
and that imageView is not added to subview of window1view
and the strange thing that does happen is that the 1. NSImageView disappears but when I check the coordinates they are on the correct spot so I don't know why it disappears.
I found out myself how to fix it.
I used instead of creating the ImageView with:
[rahmen objectAtIndex:indexRahmen];
I used:
for (NSImageView* ImageView in rahmen)
and It works fine with that.
Sadly I don't know the reason why it disappeared with the other method

Subclass of UIPageControl refresh only after uiscrollview move, not before

the problem I've met today is with my subclass of UIPageControl. When I initialize it, the frame (specifically the origin) and image of dots stays default, which is the problem, since I want it to change right after initialization. However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images.
What could be the problem?
Code:
CustomPageControl.m
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
activeImage = [UIImage imageNamed:#"doton.png"];
inactiveImage = [UIImage imageNamed:#"dotoff.png"];
return self;
}
- (void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView *dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
[dot setFrame:CGRectMake(i * 13.5, 1.5, 17, 17)];
}
}
- (void)setCurrentPage:(NSInteger)currentPage
{
[super setCurrentPage:currentPage];
[self updateDots];
}
#end
ChoosingView.m - init part
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 160, 300)];
[scrollView setBackgroundColor:[UIColor clearColor]];
[scrollView setDelaysContentTouches:NO];
[scrollView setCanCancelContentTouches:YES];
[scrollView setClipsToBounds:NO];
[scrollView setScrollEnabled:YES];
[scrollView setPagingEnabled:YES];
[scrollView setShowsHorizontalScrollIndicator:NO];
[scrollView setShowsVerticalScrollIndicator:NO];
pageControl = [[CustomPageControl alloc] initWithFrame:CGRectMake(200, 300, 80, 20)];
[pageControl setBackgroundColor:[UIColor clearColor]];
pageControl.numberOfPages = 6;
[pageControl setCurrentPage:0];
the last line is when I would expect the UIPageControl to refresh, however that does not happen.
Does this happen with the standard UIPageControl implementation?
Your problem states that your objects subViews (eg the UIImageViews) rects/size are not initialising to your desired size/position.
I implemented this code in my project with a nib rather than programmatically and I needed to call -(void)updateDots to set it as its initial condition was the standard dots..
I dont see how the UIScrollView has any bearing impact on this unless somehow its linked to your -(void)updateDots function (E.g. your setting the currentIndex of your custom page control). You state, "However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images."
Because they "jump to the right position with correct images" it means that your -(void)updateDots function must be getting called. I dont see any other explanation.
Also your iteration loop assumes that all the UIViews in your .subViews array are UIImageViews, although fairly safe to assume this, I would check to see if the UIView is a UIImageView with reflection.

How to show several subviews?

I'm having what I think is a bit of a newbie problem...
I've made a dynamic scroll view where images and labels are programmatically added as subviews. Problem is that only the last one I'm adding as a subview is showing.
Also when I read about "addSubview:" it says "Adds a view to the end of the receiver’s list of subviews." Does this mean only the last added subview is supposed show? In that case how do I make both visible?
Thanks in advance,
Tom
CODE:
for(int i = 0; i < [famorableArray count]; i++){
UIButton *famorableButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[famorableButton setFrame:CGRectMake(0.0f, 5.0f, 57.0f, 57.0f)];
[famorableButton setImage:personLogo forState:UIControlStateNormal];
NSString *famString = [NSString stringWithFormat:#"%#", [[[famorableArray objectAtIndex:i] substringFromIndex:8] capitalizedString]];
NSLog(#"%#", famString);
UILabel *famLabel = [[UILabel alloc] initWithFrame:CGRectZero];
famLabel.text = famString;
NSLog(#"Test2 %#", famLabel.text);
// Move the buttons position in the x-demension (horizontal).
CGRect btnRect = famorableButton.frame;
btnRect.origin.x = totalButtonWidth;
[famorableButton setFrame:btnRect];
CGRect labelRect = famLabel.frame;
labelRect.origin.x = totalButtonWidth + 28.5f;
[famLabel setFrame:btnRect];
// Add the button to the scrollview
[famScroll addSubview:famLabel];
[famScroll addSubview:famorableButton];
// Add the width of the button to the total width.
totalButtonWidth += famorableButton.frame.size.width + 30;
}
[famScroll setContentSize:CGSizeMake(totalButtonWidth, 79.0f)];
For each added subview you have to set the frame property. The frame is the view's position in the superview. "Adding to the list of subviews" does not imply any automatic layout. So I assume that all your subviews are visible but overlap.
My bad... It was a fail editing after copy paste. Accidentally used btnRect as a frame for the label too. Thanks for your effort anyways #Martin :)