Why does a UIWebView instance not call scrollViewDidScroll? - iphone

iOS documention says, that the UIWebView class conforms to UIScrollViewDelegate. But an UIWebView instance does not call the scrollViewDidScroll method of its controller. The delegate is set just right by
[webView setDelegate:self];
and webViewDidFinishLoad is called successfully. The controller implements both delegates, UIWebViewDelegate and UIScrollViewDelegate, like this:
#interface WebviewController : UIViewController<UIWebViewDelegate, UIScrollViewDelegate>{
UIWebView *webView;
}
Browsing SO leads to that category solution:
#implementation UIWebView(CustomScroll)
- (void) scrollViewDidScroll:(UIScrollView *)scrollView{
[self.delegate scrollViewDidScroll: scrollView];
}
#end
That category approach does basically the same: Calling the delegate's scrollViewDidScroll method. So why does the the first approach not work?

My guess is you set up delegate only for UIWebView.
Try setting delegate of scrollView.
webView.scrollView.delegate = self
it should be ok.

This is not correct. If you will use:
#implementation UIWebView(CustomScroll)
- (void) scrollViewDidScroll:(UIScrollView *)scrollView{
[self.delegate scrollViewDidScroll: scrollView];
}
#end
You will lose focus when try to enter data on page on iOS7 and more
You need to implement custom class for UIWevView and overwrite scrollViewDidScroll:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView{
[super scrollViewDidScroll:scrollView];
[((id<UIScrollViewDelegate>)self.delegate) scrollViewDidScroll:scrollView];}

Related

Delegate method in the set delegate object not responding

I have a somewhat similar concern with this question a custom delegate method inside didSelectRowAtIndexPath.
However, in my case before getting to the delegate object which is a UIViewController named BarCodeViewController, I should first pass by 2 view controllers from the initial view controller which is the CardViewController which is a table view controller. I'm setting the delegate object for my custom delegate through this:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
CardDetailsViewController *details = [self.storyboard instantiateViewControllerWithIdentifier:#"cardDetails"];
Card *selectedCard = [self.myWallet objectAtIndex:indexPath.row]; // I want this selected card to be accessible until the user clicks another card or during end of program.
[self.navigationController pushViewController:details animated:YES];
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
[self setDelegate:self.barCodeVC]; // barCodeVC is declared in CardWalletViewController.h as #property (nonatomic, strong) BarCodeViewController *barCodeVC;
if (self.delegate) {
NSLog(#"delegate is not nil");
}
}
and this is how I instantiate the view controller which I set as the delegate object
- (void)viewDidLoad
{
[self setBarCodeVC:[self.storyboard instantiateViewControllerWithIdentifier:#"myBarcodeVC"]];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
And in my delegate object, which is the BarCodeViewController I implement the delegate method
#import "CardWalletViewController.h"
#interface BarCodeViewController () <CardWalletDelegate>
#end
#implementation
- (void)cardWalletViewController:(CardWalletViewController *)sender withCurrentCard:(Card *)currentCard
{
Card *myCurrentCard = currentCard;
NSLog(#"This is my current card: %#", myCurrentCard);
}
#end
I think I am able to set my delegate object, but then the delegate method is not being implemented for I don't see in the console the NSLog(#"this is my current......"); when I reach the BarCodeViewController.
Advice please.
That's not a standard use for delegate and it's hard to tell what you really want to happen. but, it looks like your code...
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
[self setDelegate:self.barCodeVC];
Is making the call on whatever the "old" delegate is (before setting it to barCodeVC). Are you really trying to make the call on the "new" delegate? Should it be...
[self setDelegate:self.barCodeVC];
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
EDIT
What I am saying is that you are sending a message to the delegate in this line...
[self.delegate cardWalletViewController:self withCurrentCard:selectedCard];
and THEN you are setting the delegate to barCodeVC
[self setDelegate:self.barCodeVC];
So, the message is actually being sent to whatever the delegate was set to before it was set to barCodeVC (another view controller, or nil, or...). Maybe that's what you want to happen, but it looks suspicious.

Problems when overriding scrollViewDidScroll, but not all the other methods of UIScrollViewDelegate

Okay, so I did all the research for this issue but none of the existing solutions seem to address my problem, so here it is:
I have a custom class that extends UIScrollView (and contains a UIView)
I'd like to override the scrollViewDidScroll method from UIScrollViewDelegate (but not all the methods)
I have already tried implementing the code from this issue: How to subclass UIScrollView and make the delegate property private but for some reason, it doesn't do anything (the custom method that was overridden never gets called). I also know that you don't have to implement all the methods from UIScrollViewDelegate if you create a custom delegate class that implements the protocol (as per iPhone: Do I need to implement all methods for UIScrollViewDelegate (or any delegate)) - but when I do this:
MyScrollViewDelegate.h
#interface MyScrollViewDelegate: NSObject <UIScrollViewDelegate>
-(void)scrollViewDidScroll:(UIScrollView *)scrollView;
#end
MyScrollViewDelegate.m
#implementation MyScrollViewDelegate
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"Custom scrollViewDidScroll called.");
// -- some more custom code here --
// ...
}
#end
In the subclass which extends UIScrollView
// this scrollview is initiated by the NIB
- (void)awakeFromNib
{
...
[self setDelegate:[[MyScrollViewDelegate alloc] init]];
}
But while it compiles and runs, when I try to scroll the scrollable view, it crashes with EXC_BAD_ACCESS and a cryptic "(lldb)" message in the debug console.
So I'm bit at a loss here what to do.
I do have an implementation of How to subclass UIScrollView and make the delegate property private that works. My guess why your code didn't do anything: double check if you actually set the scroll view's contentSize to something bigger than your view's size. If it is smaller then there is no scrolling, just bouncing, and the scrollViewDidScroll is not called.
For you code, you actually have two issues in one line. First, the delegate property of UIScrollView is of type assign. That is if the delegate class is not retained somewhere else it will disappear in some time and you will get EXC_BAD_ACCESS. Second, by assigning [[MyScrollViewDelegate alloc] init] to the delegate and not releasing that object you create an orphan object which reference count is 1 and that will never be released. My guess is that the system recognizes the orphan object in run-time and cleans it up, after that you get your EXC_BAD_ACCESS when the delegate is sent a message.
If you prefer to use your version with separate delegate I would fix it as follows:
#interface MyScrollView: UIScrollView
{
id<NSObject, MyScrollViewDelegate> dlgt;
...
}
...
#end
#implementation MyScrollView
- (void)awakeFromNib
{
...
dlgt = [[MyScrollViewDelegate alloc] init];
[self setDelegate:dlgt];
}
-dealloc
{
[dlgt release];
[super dealloc];
}
#end
Still, don't forget to set the contentSize to something bigger than the view bounds. Otherwise there will be no scrolling and no delegate calls.

iPhone - webViewDidFinishLoad not called

I have a simple View into IB that contains just a UIWebView and a UIButton.
The webView is retained, connected, not nil, the delegate property os also connected, the Controller is UIWebViewDelegate compliant and set in the .h.
in ViewDidLoad, I use [self.webView loadHTMLString:htmlContent baseURL:nil];
The content loads, but webViewDidFinishLoad is never triggered, neither didFailLoadWithError, neither webViewDidStartLoad.
How can I know when my content has finished loading, as it take a short time, but still a time to load (and I need to do things during that time on display, for an example not showing the button) ?
#interface MyController : UIViewController <UIWebViewDelegate> {
UIWebView* webView;
}
#property (nonatomic, retain) IBOutlet UIWebView* webView;
#end
#implementation MyController
#synthesize webView;
- (void)viewDidLoad
{
[super viewDidLoad];
constructing the htmlString
id a = self.webView.delegate; // for debug
[self.webView loadHTMLString:htmlContent baseURL:nil];
}
#end
EDIT :
I've deleted the whole XIB and built it from scrathc : no more problem. IB sucks sometimes.
You need to implement the UIWebViewDelegate protocol in your class and set the delegate property of self.webView = self. You will need to implement non-optional delegate methods to the class, plus the webViewDidFinishLoad, didFailLoadWithError and webViewDidStartLoad methods.
I've deleted the whole XIB and built it again from scratch : no more problem. IB sucks sometimes.
Just a sort of stab in the dark. As far as your code above is concerned, you still haven't set the responding object to the webView delegate, you have set a pointer to the webView delegate object but when the webView goes to respond to the delegate it will still be nil.
You should probably have: self.webView.delegate = a; but even then I don't know if that will work because I don't know what a is, is it the object that will respond to the delegate call backs?
You have to implement the UIWebViewDelegate protocol in your class and set self.webView.delegate=self;

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.

iOS - Scrolling two UITextViews simultaneously

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.