I found some answer that quite solve my questions, but seems that I'm missing some steps. I'm just trying to intercept the "ViewDidBlablabla" events of a ScrollView, so I created a custom UIScrollView class.
MyScrollView.m
#import "MyScrollView.h"
#implementation MyScrollView
- (id)initWithFrame:(CGRect)frame{
NSLog(#"initWithFrame");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView{
NSLog(#"scrollViewWillBeginDragging");
//scrollView.contentOffset
}
-(void)scrollViewDidScroll:(UIScrollView*)scrollView{
NSLog(#"scrollViewDidScroll");
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
NSLog(#"scrollViewDidEndScrollingAnimation");
}
#end
MyScrollView.h
#import <UIKit/UIKit.h>
#interface MyScrollView : UIScrollView <UIScrollViewDelegate>
#end
When I alloc my custom Scroll View I do get the console message "initWithFrame", but there's no way to get to the other events. What am I missing?
If you need some other pieces of code feel free to ask, but since I get to my custom "initWithFrame" method, I suppose that the error should be here.
Try setting your view as the delegate of itself:
if (self) {
// Initialization code
self.delegate = self;
}
Technically, you do not need to inherit UIScrollView to respond to scrolling events: it is sufficient to set the delegate to an implementation of <UIScrollViewDelegate>.
Also, the scrollViewDidEndScrollingAnimation: method has been gone for about two years, so it is not going to be called on modern iOS installations.
Related
I'm trying to understand how Subclassing works in Cocoa.
I've created a new Cocoa Application Project in XCode 5.1.
I drag a new Custom View onto the main window.
I create a new Objective-C class CustomViewClass and set it as a Subclass of NSView. This generates the following :
CustomViewClass.h
#import <Cocoa/Cocoa.h>
#interface CustomViewClass : NSView
#end
CustomViewClass.m
#import "CustomViewClass.h"
#implementation CustomViewClass
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
NSLog(#"Custom View initialised");
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
#end
Note that I added the NSLog(#"Custom View initialised"); line so I can track what is going on.
In interface Builder, I select the Custom View and within the Idenditiy Inspecter set it's custom Class to CustomView. Then I run the Application.
As expected I get a Custom View initialised message in the Console.
I do exactly the same with an NSTextField adding it to the window, creating a new class TextFieldClass and the NSTextField custom Class is to TextFieldClass. I also add a NSLog(#"Text Field initialised"); in the same place as above to track things.
However when I run the App, I only get the Custom View initialised message in the Console and not the NSLog(#"Text Field initialised");message.
So initially I think that NSTextField doesn't recieve the initWithFrame message when it is created. So I add an initialiser to TextFieldClass :
- (id)init {
self = [super init];
if (self) {
// Initialization code here.
NSLog(#"Text Field initialised");
}
return self;
}
However this still doesn't seem to get called.
I assumed therefore that NSTextField just wasn't being subclassed. However, when I add this method to TextFieldClass :
-(void)textDidChange:(NSNotification *)notification {
NSLog(#"My text changed");
}
Run the app and lo and behold, every time I type in the text field I get the My text changed message in the Console.
So my question is, what is going on here? How does the NSTextField get initialized and how can you override it's initialiser?
Why does the Custom View seem to act differently to the NSTextField?
Source code here
For your first question, NSTextFiled gets initialised via
- (id)initWithCoder:(NSCoder *)aDecoder
In this case, you have dragged a NSTextField from the palette and then changed the class to your custom text field class in the identity inspector. Hence the initWithCoder: will be called instead of initWithFrame:. The same is true for any object (other than Custom View) dragged from the palette
Instead, if you drag "Custom View" from the palette and change the class to your custom text field class, the initWithFrame: will be invoked.
The CustomViewClass you have created is the second case, hence initWithFrame: is invoked. The TextFieldClass is the first case, hence initWithCoder: is invoked.
If you use the Interface Builder in XCode, you should use awakeFromNib to initialise your subclass.
- (void)awakeFromNib
{
// Your init code here.
}
If you want to use your subclass programatically and using the interface builder, then use code like this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initView];
}
return self;
}
- (void)awakeFromNib
{
[self initView];
}
- (void)initView
{
// Your init code here
}
Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.
I subclassed NSSecureTextField, and overrided -(BOOL)becomeFirstResponder,but it only works well when my custom NSSecureTextField get focus at the first time.
I think it is not a problem of NSSecureTextField. It is a problem of how a control get focused.
Create a new project and only drag two NSSecureTextField on your view. Set the custom class of one of them to MySecureTextField defined below and keep the other one default. Run the project and change focus between the two NSSecureTextField, you will see the "Get focus" printed when the custom one get focus each time.
Back to your program, please check if the NSSecureTextField is lost focus? Does the resignFirstResponser called?
#import "MySecureTextField.h"
#implementation MySecureTextField
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (BOOL)becomeFirstResponder
{
NSLog(#"Get focus");
return [super becomeFirstResponder];
}
#end
I was trying to learn UIPageViewControllers and hit an Issue which I couldn't resolve.
This is what I tried to do:
Steps:
I simply created 2 view controllers and a page view controller in
StoryBoard.
Then I added some code to the File's Owner of PageViewController to
behave as a dataSource and delegate to itself.
When I ran, things worked well.
I added some buttons, and text fields to the second view controller.
I ran, worked well.
Now I added a text view to the second view controller and ran. When I tried to write something inside the text view, the page control jittered and moved to first view controller.
Has anyone experience this ever?
#interface AMPageViewController : UIPageViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
#end
The implementation:
#import "AMPageViewController.h"
#interface AMPageViewController ()
{
UIViewController *mainController;
UIViewController* socController;
}
#end
#implementation AMPageViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle: nil];
mainController = (UIViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"First"];
socController = (UIViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"Second"];
[self setViewControllers:#[mainController]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
self.dataSource = self;
self.delegate = self;
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
if (viewController == socController )
return mainController;
else return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
if (viewController == mainController )
return socController;
else return nil;
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
return 2;
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
#end
If you want to download and try the project
I've investigated a lot on this problem.
It seems a bug related to the internal (private) UIScrollView of the UIPageViewController.
If you search on StackOverflow you will find a lot of post with this problem and no solutions...
I seems that the UITextView (which is an UIScrollView and, AFAIR, has an internal UIWebView), sends some strange message to it's superviews chain, that makes the private UIScrollView of the UIPageViewController scrolling to the top-left corner.
I would have tried to block this message using method swizzling, but this is probably not ok for AppStore. So I tried other things.
The final solution is very simple: simply, embed your UITextView inside an UIScrollView!
This is a link to your project updated
If you do so, you'll solve the problem!
Try and let me know
EDIT:
How did I arrive to this solution:
An intuition.
A lot of debug and stack traces had make me think that the problem was related to a bug in the "nesting UIScrollView" system and some messages sent from the inner view to its superview.
UITextView inherits from UIScrollView and has inside an UIWebDocumentView (private) which is another UIScrollView. During the debug I saw a lot of messages (private methods) like "relayout superview" sent to the upper UIScrollView's. So, for some reason, the inner scroll view (UIWebDocumentView?) was sending a message/event to it's superview. This message/event (probably because of a bug) was not stopping to the external UITextView, and was forwarded to the UIScrollView handled by UIPageViewController.
Embedding the UITextView inside a simple UIView was not enough, because UIView forward the message to it's superview if it can't handle.
I thought: UIScrollView probably doesn't (otherwise it wouldn't simple to nest UIScrollViews), so I tried and it worked.
This is all a supposition because I stopped inspecting, I will have a more in-depth look this week.
Build target iOS-7.0.
The scrollview trick wasn't working for me. Tried to embed the textview in a scrollview through storyboard and code but no luck.
Simply delaying the call to the textview did it. Not very elegant, but its the only thing I've gotten to work so far.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.textView becomeFirstResponder];
});
}
Tested, working on my iPhone 5 and my ultra-slow iPhone4. Although its totally possible that whatever implementation detail enables the textview to become the responder could take longer than the set time. So keep in mind this isn't exactly bulletproof.
--EDIT--
Well... it's working on my iPhone 4 beater with a delay of 0.0000000000000001
you did not set before and after view controllers and also look in to first responder for socController
How can I overwrite the initWithRootViewController method in a UINavigationController?
The only methods generated by xcode for me where methods suchs as loadFromNibName and loadView. These methods don't get called and I need to add an NSNotification to the navigationcontroller at startup.
I know it looks a little like the following but I don't know what put in the body of the method
- (id)initWithRootViewController:(UIViewController *)rootViewController
{
// what goes here?
}
EDIT
I guess the question really is "how do you customize a UIViewCOntroller during initialization"
Edit 2
My Navigation Controller header
#interface AccountViewNavigationController : UINavigationController {
}
#end
Instantiating my UINavigationController Like so will result in no startup methods hitting break point
accountViewNavController = [[UINavigationController alloc] initWithRootViewController:accountView];
Where as if I instantiate like so loadView does get called.... but it gets called numerous times
accountViewNavController = [[UINavigationController alloc] init];
[accountViewNavController initWithRootViewController:accountView NO];
I'm highly confused by this stage.
Use the same basic structure you use for overriding any other init method:
- (id)initWithRootViewController:(UIViewController *)rootViewController
{
if ((self = [super initWithRootViewController:rootViewController])) {
// Your modifications go here
}
return self;
}
Do note that Apple claims UINavigationController is "not intended for subclassing", but they don't absolutely forbid it. I guess that means "don't try to change how the class works by messing with the internal message flow".