iOS - Scrolling two UITextViews simultaneously - iphone

I've done some looking around but I haven't been able to find anything that clearly explains how I could simultaneously scroll two un-editable UITextViews. I think I may need to use either scrollRangeToVisible, or setContentOffset, though I could not get either of them to work.
Does anyone have any examples/samples, or documentation regarding this that they could point me towards?
EDIT: To clarify, I would like to be able to scroll one UITextView, and have the changes as a result of the scrolling reflected on a second UITextView as well.
Thanks!

Use the UIScrollViewDelegate methods to get information about scroll actions of the first scroll view and then scroll the second programmatically like that:
- (void) scrollViewDidScroll:(UIScrollView *)view1 {
scrollView2.contentOffset = view1.contentOffset;
}

React in
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
and set the other's scrollView setContentVisible according to scrollView.contentOffset.
Just be aware that some methods of UIScrollView will invoke scrollViewDidScroll even if called programmatically. This applies to scrollRangeToVisible and will end up in a loop unless you take action to prevent this loop. I don't think that setContentOffset or setting scrollView2.contentOffset = CGPointMake(..,..) does call scrollViewDidScroll. However, I would not sign this in blood. Be prepared to see a loop and take actions to avoid it. (such as boolean instance variable set before calling setContentOffset and re-set in scrollViewDidScroll followed by return;)

Just continuing with previous answers, to give some more information, I generated this code:
In the interface (.h):
#import <UIKit/UIKit.h>
#interface DoubleTextViewController : UIViewController <UITextViewDelegate>
#property (strong, nonatomic) IBOutlet UITextView *textView1;
#property (strong, nonatomic) IBOutlet UITextView *textView1;
#end
In your implementation (.m):
Use this viewDidLoad function after defining the corresponding properties and global variables.
#import "DoubleTextViewController.h"
#define TEXT_VIEW_1_TAG 1001
#define TEXT_VIEW_2_TAG 1002
#interface DoubleTextViewController () {
BOOL isScrolling;
}
#end
#implementation DoubleTextViewController
#synthesize textView1, textView2;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view..
isScrolling = NO;
[self.textView1 setTag:TEXT_VIEW_1_TAG];
[self.textView2 setTag:TEXT_VIEW_2_TAG];
[self.textView1 setDelegate:self];
[self.textView2 setDelegate:self];
}
And add this function for simultaneous scroll.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (isScrolling) {
return;
}
isScrolling = YES;
if (scrollView.tag == TEXT_VIEW_1_TAG) {
[self.textView2 setContentOffset:scrollView.contentOffset animated:NO];
} else if (scrollView.tag == TEXT_VIEW_2_TAG) {
[self.textView1 setContentOffset:scrollView.contentOffset animated:NO];
}
isScrolling = NO;
}
As proposed by Hermann Klecker, the isScrolling variable stops scrolling loops and makes the user experience nicer. Using the code proposed by Fabian Kreiser makes the scroll stops as soon as the user leaves the finger, making it strange.

Related

iOS iPad App: Delegates not called for ViewController with two UIScrollViews and a UIPageControl (delegate functions for paging NOT CALLED)

For my iPad App, I have a main ViewController which contains two UIScrollviews and a UIPageControl.
The Problem is that the delegates for the paging are not getting called.
Here is the layout:
Selecting a button in the lower thumbScrollView needs to update the image in the mainScrollView (this works) Swiping the thumbScrollView or picking a dot on the pageControl needs to "page" the thumbScrollView to show the next previous set of buttons. The swiping does not work because the delegate functions are just not getting called.
I declare the scrollviews and pagecontrol as follows in my VC
#property (strong, nonatomic) IBOutlet UIScrollView *mainScrollView;
#property (strong, nonatomic) IBOutlet UIScrollView *thumbScrollView;
#property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
The ViewController implements UIScrollViewDelegate
#interface MyViewController : UIViewController<UIScrollViewDelegate>
And I implement the following UIScrollViewDelegate delegate functions in my VC's .m file.
- (void)scrollViewDidScroll:(UIScrollView *)sender;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
The view appears but when I swipe across the buttons I do not see the delegate functions above getting called.
I have not found a solution to this in StackOverflow although I have factored in advise from other related posts for other aspects of this (ex. the logic to distinguish which scrollview has initiated the action etc)
ADDING DETAILED CODE HERE (as requested by #HeWas)
This is the header file for the Main View Controller that controls the two scrollviews and pagecontrol (RELEVANT EXCERPTS - TELL ME IF YOU NEED MORE)
// ImageBrowseViewController.h
// (NOTE - In Interface Builder I have added a tag attribute of 0 to mainScrollView
// and 1 to thumbScrollView, to enable me to distinguish which scrollView the delegate
// needs to respond to)
#define TAG_MAIN_SCROLLVIEW 0
#define TAG_THUMB_SCROLLVIEW 1
#interface ImageBrowseViewController : UIViewController<UIScrollViewDelegate>
{
UIButton* currentlySelectedButton;
UIScrollView *mainScrollView;
UIScrollView *thumbScrollView;
UIPageControl* pageControl;
BOOL pageControlBeingUsed;
}
#property (strong, nonatomic) IBOutlet UIScrollView *mainScrollView;
// … connected as outlet in IB to mainScrollView
#property (strong, nonatomic) IBOutlet UIScrollView * thumbScrollView;
// … connected as outlet in IB to thumbScrollView
#property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
// … connected as outlet in IB to pageControl
…
-(IBAction)changePage; //Touch up Inside IBAction connected to pageControl
…
#end
This is the implementation file for the Main View Controller that controls the two scrollviews and pagecontrol (RELEVANT EXCERPTS - TELL ME IF YOU NEED MORE)
//
// ImageBrowseViewController.m
//
…
#synthesize mainScrollView;
#synthesize thumbScrollView;
#synthesize pageControl;
// UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)sender {
if ( [sender tag] == TAG_THUMB_SCROLLVIEW ) {
// This is the thumbScrollview
// Update the page when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.thumbScrollView.frame.size.width;
int page =
floor((self.thumbScrollView.contentOffset.x - pageWidth / 2) / pageWidth)
+ 1;
self.pageControl.currentPage = page;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
pageControlBeingUsed = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlBeingUsed = NO;
}
- (IBAction)changePage {
// Update the scroll view to the appropriate page
CGRect frame;
//frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.x = self.thumbScrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.thumbScrollView.frame.size;
[self.thumbScrollView scrollRectToVisible:frame animated:YES];
// Keep track of when scrolls happen in response to the page control
// value changing. If we don't do this, a noticeable "flashing" occurs
// as the the scroll delegate will temporarily switch back the page
// number.
pageControlBeingUsed = YES;
}
You code all looks 100% (aside from this typo: #synthesize floorplanThumbScrollView;, but that isn't your problem).
I am sure that the answer is that you have not correctly wired your scrollview DELEGATES in IB.
This is the clue:
"Yes I have set all three in Interface Builder. So mainScrollView, thumbScrollView, and pageControl are wired in IB to the above declarations in the VC's .h file."
You need 2 connections between your ViewController and your scrollViews.
(1) ctrl-drag FROM viewController TO scrollView, connect to IBOutlet property.
This is what you have done.
(2) ctrl-drag FROM scrollView TO viewController, connect to delegate.
I do not think you have done this.
Explanation of step 2
UIScrollView has a built-in property called 'delegate'. The scrollView uses this property to send messages to it's delegate. You set this delegate in interface builder (step 2) or you can do it in code. For example in your viewController you could do this:
[myScrollView setDelegate:self];
which would set the viewController as the delegate for myScrollView. If you do it by linking in Interface Builder you don't need this code (and IB doesn't create any).
Either way what this actually does is set scrollView's delegate iVar to a pointer to the viewController. The great thing about using delegates like this is that the delegator (UIScrollView) doesn't have to know anything about the delegatee (in this case your UIViewController). This allows us to reuse UIScrollView so long as we observe it's delegate protocol.
Whenever the scrollView needs to notify it's delegate, internally it sends a message like this..
[self.delegate scrollViewDidScroll:self];
(you don't see that, it's in the scrollView's implementation).
The object that you have set as the delegate to scrollView needs to implement all of the required methods that the scrollView's delegate protocol declares, and can choose to implement any of the optional delegate methods. Here is the protocol
To work out which methods are required, read the UIScrollView class reference, which tells you this:
The UIScrollView class can have a delegate that must adopt the UIScrollViewDelegate protocol. For zooming and panning to work, the delegate must implement both viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale:; in addition, the maximum (maximumZoomScale) and minimum (minimumZoomScale) zoom scale must be different.
Everything else in the protocol is optional.
This delegate pattern is one you can easily implement yourself for your own object reuse, and is one of the most common ways of passing messages between decoupled objects in objective-C.

glkView drawInRect not called

I am learning OpenGLES and I am trying to put a GLKViewer inside an UIViewController.
I know I can come around the main issues by using GLViewController, but I am trying to learn how to do it this way.
I found this question, Nesting GLKView into UIViewController and Nested GLKView and GLKViewController but I must be missing something even though I think I am doing all the right steps because when I run my project, I am not getting to the drawInRect print line.
In the storyboard I am pointing the ViewController as the delegate of the glkview component.
I tried to keep the code as simple as possible and any help will be apreciated:
MyController.h
#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>
#interface MyGLController : UIViewController <GLKViewDelegate>
{
GLuint vertexBufferID;
}
#property (weak, nonatomic) IBOutlet GLKView *glview;
#property (strong, nonatomic) GLKBaseEffect *baseEffect;
#end
MyGLController.m
#import "MyGLController.h"
#implementation MyGLController
//#synthesize baseEffect;
-(void) viewDidLoad{
[super viewDidLoad];
self.glview.context = [[EAGLContext alloc] initWithAPI:
kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.glview.context];
printf("View Loaded");
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
printf("DrawInRect");
}
#end
* Update *
As far as I can tell the glkview is hooked up properly as suggested per and added the josh-knapp and called setNeedsDisplay.
In case there's something I am missing, I have uploaded a copy of the project here: https://github.com/jcrogel/OpenGLDebug.git
I am a total noob in this so I apologize for any silly oversight :)
You didn't say you hooked up the glview property of MyGLController in the storyboard, so verify that.
Next, you're setting up the glview context after it loads. Without a GLKViewController, there is nothing telling the glview it needs to be drawn. Make sure you call [self.glview setNeedsDisplay] somewhere after the context is set up.
I had a similar problem.
By default, xcode sets the enable setNeedsDisplay checkbox to NO in GLKView property list.
Once I changed it, it worked.
I see this question was already answered. But I had the same problem, with a different solution. I figured I'd post the info here on the off chance others might be able to benefit from it.
Problem Description
While using GLKView and GLKViewController, the render loop function (drawInRect) is called once, but not called again.
Possible Cause
Some methods to CViewController have been implemented incorrectly, without calling their supers. The following code illustrates three such functions. The examples below have the three functions implemented correctly, calling their supers.
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:NO];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:NO];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:NO];
}

How to change the frame of UIKeyBoard programmatically in iOS

Well, i have gone through some decent goggling before posting this question but was unsuccessful in finding the right answers.
I cant really explain my entire app scenario here as it is a bit complex to explain. So, let me make this question very very simple. How can i change the frame of the UIKeyBoard.i.e. I want the UIKeyBoard to rotate or translate 90 degrees upwards to support my view position.
Is there a way out for me?
You can't change the default keyboard. You can, however, create a custom UIView to be used as keyboard replacement by setting it as inputView on, for example, a UITextField.
While creating a custom keyboard takes a bit of time, it works well with older iOS versions (inputView on the UITextField is available in iOS 3.2 and later) and supports physical keyboards (the keyboard is hidden automatically if one is connected).
Here's some sample code to create a vertical keyboard:
Interface:
#import <UIKit/UIKit.h>
#interface CustomKeyboardView : UIView
#property (nonatomic, strong) UIView *innerInputView;
#property (nonatomic, strong) UIView *underlayingView;
- (id)initForUnderlayingView:(UIView*)underlayingView;
#end
Implementation:
#import "CustomKeyboardView.h"
#implementation CustomKeyboardView
#synthesize innerInputView=_innerInputView;
#synthesize underlayingView=_underlayingView;
- (id)initForUnderlayingView:(UIView*)underlayingView
{
// Init a CustomKeyboardView with the size of the underlying view
// You might want to set an autoresizingMask on the innerInputView.
self = [super initWithFrame:underlayingView.bounds];
if (self)
{
self.underlayingView = underlayingView;
// Create the UIView that will contain the actual keyboard
self.innerInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, underlayingView.bounds.size.height)];
// You would need to add your custom buttons to this view; for this example, it's just red
self.innerInputView.backgroundColor = [UIColor redColor];
[self addSubview:self.innerInputView];
}
return self;
}
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// A hitTest is executed whenever the user touches this UIView or any of its subviews.
id hitTest = [super hitTest:point withEvent:event];
// Since we want to ignore any clicks on the "transparent" part (this view), we execute another hitTest on the underlying view.
if (hitTest == self)
{
return [self.underlayingView hitTest:point withEvent:nil];
}
return hitTest;
}
#end
Using the custom keyboard in some UIViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
CustomKeyboardView *customKeyboard = [[CustomKeyboardView alloc] initForUnderlayingView:self.view];
textField.inputView = customKeyboard;
}

iOS iPhone is it possible to clone UIView and have it draw itself to two UIViews?

I'm thinking of a way to have a UIView render itself onto another UIView as well as the first one. So I have my main UIView with it's bounds, and the UIView also renders itself in some other UIView.
Is this possible ? Does it require extensive layer operations?
Don't know whats your real intention is, but this will draw the view twice, userinteraction etc. will not work on the second view. Also this solution does not take care of different frame sizes.
Header of the View you want to clone
#interface SrcView : UIView
#property(nonatomic, readonly, strong) UIView *cloneView;
#end
#interface CloneView : UIView
#property(nonatomic, weak) UIView *srcView;
- (id)initWithView:(UIView *)src;
#end
implementation of the View you want to clone
#import "SrcView.h"
#import "CloneView.h"
#implementation SrcView
#synthesize cloneView;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
[cloneView setNeedsDisplay];
}
- (UIView *)cloneView {
if (!cloneView) {
cloneView = [[CloneView alloc] initWithView:self];
}
return cloneView;
}
#end
#implementation CloneView
#synthesize srcView;
- (id)initWithView:(UIView *)src {
self = [super initWithFrame:src.frame];
if (self) {
srcView = src;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[srcView.layer renderInContext:UIGraphicsGetCurrentContext()];
}
#end
now you can just call cloneView and add it somewhere you want.
This seems to be an oft asked question here on StackOverflow. For example:
iphone, ipad Duplicate UIView - Cloned View
Copying the drawn contents of one UIView to another
UIView duplicate
Duplicate, clone or copy UIView
But if it were me doing this, my first approach would be to get a handle to the UIView I want to copy, then recursively iterate all the subviews of it and then copy & add them as subviews to the UIView I want to copy the main UIView into.
I can't imagine there's too much layer operations going on with this, but you would likely need to figure out how to programmatically re-establish outlets and/or actions.
There is no easy way to clone a view and then to update two views by one line of code. Because their underlying CALayers are different. But for duplicating a UIView, here is a new method you can use: Use UIView's method:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
This is the fastest way to draw a view. Available in iOS 7.
Create another instance of the UIView you wish to "clone" and add it as a subview to another view. You don't really clone a UIView object, you simply create another instance of it.

How to detect the rotation change when coming back to tableView from detailView?

I have a UITableViewController and DetailViewController that pops up when concrete row is selected. The problem is the tableView does not call "didRotateFromInterfaceOrientation" method when rotation is did in DetailViewController. It's logical, however I need to reload a table if orientation has changed.
I'm trying this code but it doesn't work:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if (self.interfaceOrientation != [UIDevice currentDevice].orientation){
[self handleTableViewRotation];
}
}
I'm assuming there should be some property that holds orientation of tableView that is independent of current device orientation property...
Maybe the most elegant solution is coding a protocol. Whenever your DetailViewController enters in a rotation callback, call reloadData method of your tableView.
Or... you can continue that way. In that case you have to store the tableViewController's last orientation as a instance var and compare it to the current. After that, remember setting the new orientation
EDIT: First declare a new Protocol.
#protocol RotationDelegate
- (void) didRotate;
#end
In your DetailViewController.h:
#import RotationDelegate.h
#interface DetailViewController : UIViewController {
id <RotationDelegate> rotationDelegate;
}
#property (nonatomic, assign) id <RotationDelegate> rotationDelegate;
In DetailViewController.m:
#synthesize rotationDelegate;
-(void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[rotationDelegate didRotate];
}
Now your ViewController that contains the tableView:
#interface RootViewController : UITableViewController <RotationDelegate> {
...
}
And finally implement didRotate method and call [yourTableView reloadData] inside. Remember that you have to set the rotation delegate after init your DetailViewController.