Detecting which part of a scrollview is on screen - iphone

I have scroll view to which i add a variable number of uitextviews,so that the user can see each text with a swipe.Its working all fine.But i want to display a label when the user reaches the last text.I dont know how to detect whether the user has reached the last textview.I hope im clear enough with my question.Thanks

The typical way of doing this is calculating the page the user is on and evaluating that ( this is to utilise page Controls) This is done easily with this:
int currentPage = floor(scrollView.contentOffset.x / scrollView.frame.size.width)+1;
If you simply just want to know if the user is on the last page...
if(scrollView.contentOffset.x >= scrollView.contentSize.width-scrollView.frame.size.width)
{
NSLog(#"USER IS ON THE LAST PAGE");
}
You would typically put this in:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
or:
- (void)scrollViewDidScroll:(UIScrollView *)sender;
depending on what your looking for.

Related

iOS Keyboard Location and Orientation

I'm relatively new to iOS SDK, and I'm experiencing a very bizarre issue regarding the device keyboard location and orientation for an app I'm working on. The problem is that if the keyboard is open while the user multi-tasks or the app goes into background, after the user comes back to the app, the keyboard will be be displaced (with UIKeyboardWillChangeFrameNotification being raised), but in an incorrect orientation and location.
Sometimes the keyboard shows up completely off the screen too, which is completely undesired behaviour.
My questions are:
What is the position and orientation of the keyboard dependant on? How is it controlled by iOS?
Is there a way to detect when the keyboard is being displayed off-screen regardless of the device type and screen size? I'm thinking it would be doable by tracking UIKeyboardWillChangeFrameNotification or UIKeyboardWillShowNotification.
How would I reset/set the location and orientation of the keyboard prior to displaying it? Is this even possible?
From the documentation:
Use the keys described in “Keyboard Notification User Info Keys” to get the location and size of the keyboard from the userInfo dictionary.
Keys used to get values from the user information dictionary of keyboard notifications:
NSString * const UIKeyboardFrameBeginUserInfoKey;
NSString * const UIKeyboardFrameEndUserInfoKey;
NSString * const UIKeyboardAnimationDurationUserInfoKey;
NSString * const UIKeyboardAnimationCurveUserInfoKey;
1.) Keyboard is a UIWindow, the position is dependent on the Application's Main Window.
2.) What you could do is, upon the one of the notifications UIKeyboardWillShowNotification or UIKeyboardWillChangeFrameNotification method firing, loop through the windows subviews to locate the Keyboard. In one of my applications I needed to add a subview to the keyboard. For your case you can get the frame by doing this:
//The UIWindow that contains the keyboard view - It some situations it will be better to actually
//iterate through each window to figure out where the keyboard is, but In my applications case
//I know that the second window has the keyboard so I just reference it directly
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
//Because we cant get access to the UIPeripheral throught the SDK we will just use UIView.
//UIPeripheral is a subclass of UIView anyways
UIView* keyboard;
//Iterate though each view inside of the selected Window
for(int i = 0; i < [tempWindow.subviews count]; i++)
{
//Get a reference of the current view
keyboard = [tempWindow.subviews objectAtIndex:i];
//Assuming this is for 4.0+, In 3.0 you would use "<UIKeyboard"
if([[keyboard description] hasPrefix:#"<UIPeripheral"] == YES) {
//Keyboard is now a UIView reference to the UIPeripheral we want
NSLog(#"Keyboard Frame: %#",NSStringFromCGRect(keyboard.frame));
}
}
3.) Not completely sure this is possible, but with the supplied code I gave you. keyboard is now casted to a 'UIView', which you can apply your own transforms to.
This might not be the most elegant solution, but it works well for my case.
Hope this Helps !

animateWithDuration causing problems with UITextView

Earlier today I was trying to get some text in my app to autoscroll through the lyrics of the song that was currently playing and I got an answer to use the animateWithDuration method to get the scroll to work properly. It worked fantastically until I noticed that when I use the following code, something covers up 80% of the textView in the same color as the textView background. When I swipe on the area covered (both in the simulator and on my test devices), the box (or whatever it is) disappears and all the text appears as normal. I know it's the method call of animateWithDuration, because I commented it out and the text isn't covered when I build and run with it not included in the project. Also I went into Interface Builder and changed the color of the textView to white (it was black along with the backgrounds of everything else in the view) to see if what was covering it had a black background as well. When I changed it to white, the text was still covered, this time by a white box. Again it's only the very first time I unhide the textView and populate it with the lyrics for the song. After I swipe across the covered area, any song I play the obstruction doesn't appear. Just the initial time until it's swiped away. Here is the code I'm using...
// set the textView to start at the top of the document
[textView setContentOffset:CGPointZero];
CGPoint scrollPoint = textView.contentOffset;
// set the scroll point to the length of the text view, so it knows how far down to go
scrollPoint.y= [textView.text length];
The following is what causes the text to get covered
`// animate down to that point at the rate of the length of the song minus 20 seconds (so the lyrics can catch up to the song)
[UIView animateWithDuration:(self.myMusicPlayer.duration - 20)
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^(void) {self.textView.contentOffset = scrollPoint;}
completion:nil];`
And here's a photo of the before and after swiping the boxed area.
Anyone have any idea what is covering the text or what I can do to get rid of it? Or another way I could autoscroll through the lyrics using another method besides animateWithDuration?
Here is the code I'm using to populate the textView with the lyrics.
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { // determine which row was clicked
NSUInteger row = [indexPath row];
// get the song name from the array at the position clicked
NSString *rowString = [songNames objectAtIndex:row];
// load the lyrics for the song played into the textView
// load the text file for the song selected
NSString *lyrics = [[NSBundle mainBundle] pathForResource:rowString ofType:#"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:lyrics];
// set the textView to load the lyric sheet for the song selected
self.textView.text = fileContents; // more after this, but this is all that I'm doing with the textView
EDIT: Well, I've solved the issue of the black box covering the text, though I don't understand it. While I was formatting the text file to flow better with the lyrics, my carriage returns as well as breaking lines up cause the text to get covered. I deleted the contents of the text file and re-pasted the original, resulting in none of the text being covered up. So now I guess my quest is to figure out how to format the text in the text file without having the text covered up. Any suggestions?
So apparently the text document was stored somewhere within the app, as I deleted all the text files in the resources folder and the text for the lyrics still appeared. I'm guessing it was using the size of the text files that were stored as anytime I made the text file smaller (by deleting lines from the double spacing), is where the problem came in. I formatted it how I wanted, and added a bunch of carriage returns at the end of my text document, and it works fine now.

IPhone SDK: Problem with lazy loading scrollView pictures

My app involves a scrollView containing imageViews chosen by the user through a modified ELCImagePicker. The chosen pictures will typically be high quality photos at 5 MB+ and the user will usually choose at least a dozen pictures at a time. Currently, I am loading the photos as below:
-(void)loadViewWithPage: (int)page
{
if (page > 0 && page < [Album count]) {
[scrollView addSubview:[Album objectAtIndex:page]];
}
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth) / pageWidth) + 1;
[self loadViewWithPage:page + 1];
}
Where Album is where the photos are stored as imageViews.
This works great when the user is not trying to break the app and scroll through the photos one at a time, but fails miserably when he/she tries to scroll through the entire selection. The pages are blank unless the user stops after each photo. I tried using scrollViewDidScroll ala PageControl sample, but since the photos are all huge the lag is very visible.
Is there any way of loading the photos smoothly?
I had a similar situation and dealt with it by creating a custom subclass of NSOperation which would load the images in a separate thread and then display them by calling a method on the main thread.
While the image was loading I displayed a UIActivityView
Hope that helps.
if you use scrollViewDidEndDecelerating it will only fire when the scrollview stops. I would use scrollViewDidScroll for that. (it's also used in their examples)

Change the minus image and action when UITableView is in edit mode

I have two questions:
Can we change the tap event of the (-) minus sign that appears when the table is in editmode?
Can we change the image of the minus sign that appears when the table is in edit mode?
I tried to find the solution in various posts but failed. So if you know the solution in any other post kindly paste the link over here.
You can't change the minus sign image, but if you are using it to actually delete items then I would recommend that you keep it as it is anyway. The reason is that it is a well-known icon to iPhone users. If you want to represent delete in some other way, it may be potentially confusing.
That said, if you want the left button to represent something else, then you can create a custom table cell with an image on the left. That image can be anything you want then. You can display or hide the custom icon by overriding didTransitionToState:
Quoting Chris Carrett
You can't change the minus sign image, but if you are using it to actually delete items then I would recommend that you keep it as it is anyway. The reason is that it is a well-known icon to iPhone users. If you want to represent delete in some other way, it may be potentially confusing.
However if you really want to change the icon you could try to remove it and add your own UIImageView in place.
Update:
I struggled with this but think I finally got it right.
I made a github repo as an example but this is the code hiding the old image:
- (void) removeOriginalEditControl{
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"]) {
for (UIView *subsubview in subview.subviews) {
if ([NSStringFromClass([subsubview class]) isEqualToString:#"UIImageView"]) {
[subsubview removeFromSuperview];
break;
}
}
}
}
}
Check out the full example at: https://github.com/tiemevanveen/TableViewCustomEditControls

Lazy load pages in UIScrollView

I have a UIScrollView that contains large images and am using paging to scroll between images. In order to save memory, I am loading only one image before and after the currently visible one and loading/releasing new images after a scroll has completed.
The problem occurs when one scrolls quickly and scrollViewDidEndDecelerating is not called.
How can I detect continuous scrolling?
I could check item location on every scrollViewDidScroll but this seems a bit heavy...
Perhaps a table view with custom cell content would work better for you, since it has a lot of logic built in for only loading cells that are visible as opposed to everything at once. There are lots of tutorials for how to manage table views in various ways.
My temporary solution is to cancel continuous scrolling by disabling scroll from when the user lifts the finger until scroll has completed.
-(void) scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:NO];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:YES];
}
if you are still looking for a solution... this what I do, it works really good having a good performance, too.
- (void)scrollViewDidEndDragging:(UIScrollView *)sv willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sv
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}
You have to check both scrollViewDidEndDecelerating and scrollViewDidScroll.
When to user swipes and releases it letting to have the "momentum" scroll the first will be called at the and. If the user decides to stop the scroll with his finger by tapping to the tableview (or scroll comes to the most bottom, but I am not sure about that) the second event is fired.
You can do something like this
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 1.5) / pageWidth) + 1;
}
this method is called anytime the contentoffset changed whether programmatically or not.
From this method you can check to see if 'page' is the same as your current page, if not, you know you need to load something. The trick here is making images load without holding up the scrolling of the scrollview. That is where I am stuck.
the page calculation will change to next/previous when you are half way to the next page
As per my experience, #jd291 is correct; I am using following callbacks successfully for most places
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
but, the only case when a callback is not called is that you may not had set up the delegate properly.
The WWDC 2010 has talked about this. They do a reuse of the sub scrollviews, alloc the missing image view in scrollViewDidScroll:. The video may help:
Session 104 - Designing Apps with Scroll Views