Smooth scrolling of text (Autoscroll) in UITextView iOS - ios

I am working on a project where I there is some text in UItextview. The app wants to continuous smooth scroll that text and also wants to manage its scrolling speed. I mean here the text should scroll smoothly and the app contains slider where I can manage the speed.
Below is some sample code which I am using.
- (void) autoscrollTimerFired:(NSTimer *)timer {
[self.completeText setContentOffset:CGPointMake(0, self.completeText.contentOffset.y + 1.0) animated:NO];
if (self.completeText.contentOffset.y != self.completeText.contentSize.height - self.completeText.frame.size.height) {
scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:velocityFactor target:self selector:#selector(autoscrollTimerFired:) userInfo:nil repeats:NO];
} else {
[scrollingTimer invalidate];
}
}
The Velocity factor is the number of seconds which ranges between 0.0 to 2.5.
It works nice in simulator but in device it moves with jerks or I must say like pausing at after some line.
Could you please suggest any solution here? All suggestions are welcome.

The NSTimers actually just periodically fire events into the enclosing NSRunLoop, which each thread has (or should have). So, if you have a child (or background) process running in a different thread, the NSTimers will fire against that thread's NSRunLoop instead of the application's main NSRunLoop.
NSTimer events fire on the thread where you scheduled the timer. If you scheduled the timer on the main run loop, then the timer will fire on the main thread and be “safe and synced” with input events.
So I have some suggestions for try that (I am not sure how much it will be successful in your case)
Don't use NSTimer. Try to call from main thread via selector with "afterDelay". As given in code
[self performSelector:#selector(autoScroll) withObject:nil afterDelay:1.0];
Use KVO or may be NSNotification for event triggering. (Not do it directly ,I Think better approach.)
I prefers KVO , so writing here the steps to use it :-
Make a class with a variable of NSNumber (which should accessible outside the class). Make a object of that class and add a Observer on it. (here model is object of that class).
[model addObserver:self forKeyPath:#"name" options:(NSKeyValueObservingOptionOld |
NSKeyValueObservingOptionNew) context:nil];
Implements it's delegate methods.
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Do here what ever you want to do. It will call every time whenever there will be any change in that class variable.
}
Make a method in controller "autoScroll" which called via afterDelay selector. And change the value of the NSNumber vairabe value (any logic incremental way. Doesn't affect a lot).
Hope this helps you !!! Try This ...best of luck

Related

iOS: Animation Not working in observeValueForKeyPath

I used KVO to observe changes in a frame and set another frame accordingly
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
if ([keyPath isEqualToString:KeyPathForFrameBeingObserved]) {
[UIView animateWithDuration:1 animations:^{
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
self.viewToAnimate.frame = frame;
}];
}
}
Code in the animation block is executed but animation is not working, I suspected that repeated calls to animation method could cause this but using log messages I found that animation is not working even for a single call, can anyone explain that ?
iPad 2/(iOS8.4)
I have tried including the animation method call in dispatch_async(dispatch_get_main_queue(), ^{ ... }); but animation still not working.
I have tried pushing the observed changes in queue and delaying the animation method for a second using dispatch_after to run it only using the last element in the queue to avoid repeated calls but didn't work.
KVO is working perfectly (adding the observer is already handled), but the code in the animation block is executed without animation.
It seems like you're trying to observe the property of an UIKit object. Keep in mind that:
... the classes of the UIKit framework generally do not support KVO ...
Source: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html
So, you have to check if your observeValueForKeyPath:ofObject:change:context: is calling in fact.
This is an older question but I thought I would answer in case someone else is reading this. You will need to call setFrame to set the frame of the animation instead of the simple assignment.
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
[self.viewToAnimate setFrame:frame];

CAShapeLayer path animation has serious delay

I'm attempting to create a loading screen that draws a circle while my app makes network requests. The amount drawn of the circle is meant to represent how close the request is to finishing. However there is a serious delay (~8 seconds) between the network request and the animation. After A LOT of searching I haven't found anyone that has had this problem before so I'm very desperate.
My setup right now is that an NSProgress object will be updated as a request is being made and will trigger a KVO Notification with the NSProgress object in the userInfo. This is the same method found here.
#Client.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) {
NSProgress *progress = (NSProgress *)object;
NSDictionary *userInfo = #{#"progress":progress};
[[NSNotificationCenter defaultCenter] postNotificationName:#"FractionCompleted" object:self userInfo:userInfo];
}
}
Then the view controller that is listening for the notification will update the LoadingProgressView with the fractionCompleted of the NSProgress object that it receives.
#MainViewController.m
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateProgress:) name:#"FractionCompleted" object:nil];
...
...
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
}
Now within the LoadingProgressView, the CAShapeLayer's strokeEnd property is set as the fractionCompleted value. I am using the same idea from the tutorial found here.
#LoadingProgressView.m
- (void)updateProgress:(CGFloat)frac {
_circlePathLayer.strokeEnd = frac;
}
When I actually make a request, nothing happens until about 5 seconds AFTER the request is finished. At that point the entire circle is animated at once.
I have ABSOLUTELY NO IDEA why this is happening and it's driving me crazy. I can clearly see using the debugger that the strokeEnd property is being updated in real-time and yet the LoadingProgressView refuses to re-render until much later. Any help is very much appreciated. Thanks.
EDIT: OK so a temporary solution was to fork a new thread with a delay of 0 to update the progress view for each notification. However this seems like poor thread management since I could be creating over a hundred different threads to do the same task. I'm wondering if there's anything else I can do.
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
});
}
I ran into this same problem. It seems to be caused by setting the path from a background thread. Ensuring this happens on the main thread solved the problem. Now updating the shape draws immediately.

Do something on each property change

I have several properties in my class, I would like to call saveToFile on each property change.
I prefer not to override the setter of each property. Should I override
-[NSObject methodForSelector]? What is the best way to go?
You can register as observer to the properties you want monitored. Cocoa's KVO functionality will help you here.
Basically you need to call addObserver:forKeyPath:options:context: and register to be notified when the properties change. When this happens, the runtime calls the observeValueForKeyPath:ofObject:change:context: method on the object registered as observer. You can do here the saving you want to do.
Example for registering:
for(NSString *propName in self.propsIWantMonitored) {
[self addObserver:self forKeyPath:propName change:0 context:#selector(saveToFile)];
}
and for dealing with the change of the prop values:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// make sure we don't interfere with other observed props
// and check the context param
if (context == #selector(saveToFile)) {
[self saveToFile];
}
}
and for de-registering:
for(NSString *propName in self.propsIWantMonitored) {
[self removeObserver:self forKeyPath:propName context:#selector(saveToFile)];
}
The code samples above assume you have declared an array of properties to monitor, that you use to register as observer to. You use the context parameter to determine if observeValueForKeyPath was called as a response to the observer you just registered, or not, in order not to get into conflict with other KVO registrations made from other parts of your class.
Alternative (and more energy efficient) approach to your problem
There's one caveat with the above approach: if multiple properties are set consecutively, then the saveToFile method will be called multiple times in a short period of time, which might cause performance bottlenecks and increase the energy usage of your application.
An alternative approach would be to have a dirty flag that gets set in observeValueForKeyPath: and gets reset in saveToFile. And you can have saveToFile check the flag and don't go use the file system if the object is not dirty.
You can schedule a timer that will periodically call saveToFile, this way multiple properties set at once will result in only one disk access. You can always manually call saveToFile when you feel want an immediate save.
Note. By timer I was referring to a GCD timer, as NSTimer also has a negative energy impact on your application.
What you want is called Key-Value-Observing or KVO.
You register for example a method that gets called every time the property changes.
If you have a text field and you want to listen to changes to its text, you would register like this
[self.textField addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
And in your class you would implement this method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"text"]) {
NSLog(#"Textfield changed - MAKE CHANGES HERE");
}
}
Here's a nice tutorial, if you aren't familiar with KVO:
http://www.appcoda.com/understanding-key-value-observing-coding/
Read up on Property Observers. An example in Swift:
var currentSession: Session? {
didSet {
if let session = self.currentSession {
// Write session to file.
}
}
}
For Objective-C, key-value observing might be more proper.

Coalesce or otherwise fire methods "every so often"?

The server that provides data to my app recently added a feature that allows you do to basic logging like "user selected logo" or "user is quitting".
The only place I'd like to use this is in a page with several sliders that does a calculation on the input values. This is continuous, it re-calculates the output as you move the sliders around.
Which leaves me the problem of when to call this logging method. I don't want to call it every time the numbers change, or I'll murder the server. I could put a "Calculate now" button, but that kills the entire mode-less UI I like.
Is there a way that I can coalesce calls so all the calls made within, say, 5 seconds, results in only one call to the work method? I'd also have to force the method to fire if the user does something else, like navigates away or quits the app.
You can easily add an NSTimer to the IBAction method you have for your slider. Every time that method is called, invalidate the timer and start it again. Put the analytics call in the timer's action method, which will only be called when the timer can actually complete.
For example:
#interface ViewController ()
#property (nonatomic) NSTimer *actionTimer;
#end
#implementation ViewController
- (IBAction)sliderChanged:(UISlider *)sender
{
[self.actionTimer invalidate];
NSLog(#"Slider value: %f", sender.value);
self.actionTimer = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(timerCompleted)
userInfo:nil
repeats:NO];
}
- (void)timerCompleted
{
NSLog(#"Timer completed.");
}
#end
How about a background thread that observes the calculated value and fires an update by scheduling a block on a background thread when the output value is stable, like
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(showValue:)
name:#"showValue"
object:nil];
You can also schedule the block in viewWillDisappear for the leaving the view and (I think) quit events.

Properly observing object using KVO

I have a view which is observing the properties of a single object using KVO. I have observed all the properties of the object in the view.
[person addObserver:self forKeyPath:#"name" options:NSKeyValueObservingOptionNew context:NULL];
[person addObserver:self forKeyPath:#"photo" options:NSKeyValueObservingOptionNew context:NULL];
[person addObserver:self forKeyPath:#"address" options:NSKeyValueObservingOptionNew context:NULL];
Now, when there is change in just one property, it seems to be fine but when the whole object change, the notification gets triggered 3/4 times in just fraction of seconds. I need to load the data from the network based on the changes. While a single property change creates a single network request, but if multiple properties change at the same time. It creates a queue of request for the same object. This leads to some problem. How can I observe multiple properties at the same time and load only once even if all the properties changes.
Please do help me. This is a serious trouble, I have got into.
You can use a dispatch source in Grand Central Dispatch to coalesce the property change observations so they aren't happening more often than you can handle them.
#implementation Controller
{
dispatch_source_t source;
}
- (id)init
{
self = [super init];
if (self)
{
//We are using data add source type, but not actually using the added data.
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
//Insert your network call to load data from the network.
//The event handler will only be called when another event handler is not being processed. So you won't attempt to do another network call until the last call was completed.
});
//Dispatch sources always start out suspended so you can add the event handler. You must resume them after creating them if you want events to be delivered)
dispatch_resume(source);
}
return self;
}
- (void)dealloc
{
dispatch_release(source);
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
//Tell dispatch source some data changed. It will only call the event handler if an event is not currently being handled.
//You could add a custom timer based coalesce here for when no events are currently being processed if you want to delay all initial events to potentially wait for more changes
dispatch_source_merge_data(source, 1);
}
#end
So the first property change notification triggers the dispatch source event handler. Subsequent property changes that happen while an existing event is running are queued to run as soon as the last one is complete. This means if 5 properties change in quick succession, you will get 2 network calls (instead of 5 network calls). You can add a custom timer based coalesce when no events are being processed if you would prefer to sacrifice instant responsiveness to notifications to eliminate the second network call.

Resources