OK, so I have an app with a UITextView that I want to exhibit standard Undo behaviour. I have trawled lots of tutorials and answers on this site as well as the Apple Developer Docs and I can't understand why what I have produced is not working.
Whenever the UITextView is modified (I am using a non-standard keyboard using the textView.inputView property) the method below is passed a string that is the new text required for the text view:
- (void)setText:(NSString*)text{
NSString *oldText = [textView.text mutableCopy];
if (text != oldText) {
[undoManager registerUndoWithTarget:self selector:#selector(setText:) object:oldText];
textView.text = text;
}
}
and then to implement the undo when a UIButton is pressed
[undoManager undo];
is executed.
I have declared undoManager in my header file using
NSUndoManager *undoManager;
and synthesised it in my implementation but when I press the undo UIButton nothing happens and setText is never called. Where am I going wrong?
I think the initialisation of the undoManager is still missing in your example. You declare
NSUndoManager *undoManager;
in you headerfile, but I do not see where you allocate/initialize it.
Try adding
undoManger = [NSUndoManager alloc] init];
in you i.e. "viewDidLoad" (or similar) method. Do not forget to release the object appropriate, when you don't need it anymore, for example in dealloc.
Related
Before ios5 I was able to access a UIWebView's UIScrollView delegate like this:
for (id subview in webView1.subviews){
if ([[subview class] isSubclassOfClass: [UIScrollView class]]) {
UIScrollView * s = (UIScrollView*)subview;
OldDelegate = s.delegate;//OldDelegate is type id
s.delegate = self;
}
}
Now, I know that's not the right way to do it, but at the time (as far as I understood) it was the only way to do it. iOS 5 has changed that, so I am trying to do it the iOS 5 way:
UIScrollView * s = webView.scrollView;
Olddelegate = s.delegate;
s.delegate = self;
But either way I try to do it, the value of my OldDelegate object is 0x0. It is really important that I retain this delegate value, but try as I might, I'm just getting 0x0.
Any ideas?
I'm not using ARC...
The scrollView property of UIWebView is a subclass of UIScrollView. This private class, called _UIWebViewScrollView does not use the delegate property, it is always nil. Maybe you could do some refactoring and get the real scrollview-delegate, but i'm almost 100% sure apple will reject your app doing so.
Yes, if your using arc, its possible that its doing garbage collection on your unretained Olddelegate, you can either mark the file to not use arc with -fno-objc-arc and handle the retain and release on your own like you used to. Or you can implement strong references as described here to help you retain the variable. https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
I write iOS app and use imageStore library to lazy load images and cache them in memory. (https://github.com/psychs/imagestore)
On ViewController I create imagestore instance:
imageStore = [ImageStore new];
imageStore.delegate = self;
When image loaded successfuly, imagestore call delegate method
- (void)imageStoreDidGetNewImage:(ImageStore*)sender url:(NSString*)url
that doing reloadData on tableview to redraw cells.
All works good. But there is the problem: if ViewController didUnload (go back in navigation controller) and image loaded, application finish with crash, because imagestore call method of unloaded ViewController.
I try to do following:
1) in ViewController I place this code in viewDidUnload section:
imageStore.delegate = nil;
imageStore = nil;
2) In imageStore I added checking for nil:
if(delegate != nil) {
...call delegate method
}
It works, but periodically app crash anyway.
Try putting this code on dealloc section.
imageStore.delegate = nil;
imageStore = nil;
In the same way the if clause is not necessary because any call to an nil object is ignored by the application, so if you have something like this:
id delegate = nil;
[delegate callAnyMethod];
has no effect in your application behavior, in other hand if the call of the method delegate is optional you should asure that delegate responds to selector, something like this should do the trick:
if([delegate conformsToProtocol:#protocol(yourProtocolName)] && [delegate respondsToSelector:#selector(imageStoreDidGetNewImage:url:)]) {
[delegate imageStoreDidGetNewImage:imageStore url:url];
}
Cheers!
It works, but periodically app crash anyway.
That's a contradiction. There are two possibilities:
Your fix worked, and the app is crashing for some other reason.
Your fix did not work, the app continues to crash for the same reason it was crashing before.
It's hard to know what's wrong without knowing which of these two possibilities is in fact happening. Look at the error message and the evidence from the crash, such as the stack crawl. Why is the app crashing? Does it try to dereference the delegate property somewhere without checking it first? Does it depend on the delegate doing something, so that if the delegate no longer exists that thing doesn't get done and that in turn leads to a crash? These are the kinds of things I'd look for, but again the most important thing is to start with the evidence you have and follow your nose.
I'm looking at code in a UIViewController that conforms to the UITextViewDelegate protocol and has an instance variable called someTextView.
someTextView.text = #"some text";
[self textViewDidChange:someTextView];
Is that safe? That doesn't look Kosher to me. Is it even necessary to call textViewDidChange:? Won't it get called automatically by someTextView.text = #"some text"?
I'm debugging this error iPhone Objective-C: Keyboard won't hide with resignFirstResponder, sometimes
read the discussion of textViewDidChange:
Discussion
The text view calls this method in response to user-initiated changes to the text. This method is not called in response to programmatically initiated changes.
If it's safe and a good idea to call (UIView-) delegate methods manually depends on the code inside of the method. Sometimes there are valid reasons to do this.
But your bug is most likely not caused by this snippet.
I'm messing around with the iphone sdk and with universal apps. I have UITextView in both my iphone class and a shared class I called Shared (In my Shared class the UITextVIew is called textInput).
I have a UIButton that when pressed, calls a method of the Shared class since I allocate an instance of the Shared class when app finishes launching and gets the value from the UITextView in the Shared class and sets the UITextVIew to that text. I would think that would work, however in my IBAction method for the button being pressed, I've tried it two different ways of:
[textInput setText:#"Accessing web service..."];
// textInput.text = #"Accessing web service...";
NSLog(#"self textInput: %s", [self textInput].text);
However, my NSLog shows null which I don't understand. But if I did something similar and in my UITextView of my iphone app delegate.m, if when the button is pressed, instead of calling the Shared method, I just said
textView.text = #"Hello"; // textView is the UITextVIew in my iphone class
it works. So I am having trouble seeing the disconnect. Any thoughts? Thanks.
Looks like it's just your NSLog call that's in error. NSLog uses %# as an output specifier for NSStrings.
NSLog(#"self textInput: %#", textInput.text);
I have a text field entry in my view that I would like to block access to during a background operation. I've tried using the editable property, which successfully blocks access during the background operation, but the moment I set editable to YES, the keyboard comes up and the textfield becomes the first responder. Dismissing the keyboard just after changing editable doesn't do anything:
// Broken code
textView.editable = YES;
[textView resignFirstResponder];
I've thought about adding a clear UIView that just blocks access to the UITextView after dismissing the keyboard, but that seems like overkill. Is there a correct way to handle this?
Just so people don't have to read farther than the selected answer: It turns out that this is a "known issue" in the SDK, and you can find it listed in the release notes. Using userInteractionEnabled performs the same function, as long as you make sure to dismiss the keyboard yourself.
Try textView.userInteractionEnabled = NO;
Put a UIView in front of the UITextView with a dark (or white) background color and alpha set low (like 5%) sized to fully cover the textview. Default it to hidden.
When you want the textinput disabled, send it a resignFirstResponder then show the hidden layer on top. It intercepts user inputs (and ignores it). The alpha color will make it look 'dimmed.' Once your background operation is done just set the cover view to hidden and you're good to go. If you want to get fancy you can do UIView alpha fade animations.
I'm not sure of a "correct way" but I'd probably do the transparent view solution... I agree that it seems like overkill but the simple solution is often a good answer.
Since the view gets focus upon changing the editable properties this would be easier.
the other solution that I can think of is to derive a custom UITextView and recode the editable property (or make a new method) that can accomplish what you are trying to do. This is a good object oriented solution but this could be come cumbersome.
You might also consider using a Category to add the functionality. But for either of these solutions, are still back to square one of how to accomplish what you need...
Thank god someone came up with a better response. I originally built it with the following blocker, but the userInteractionEnabled BOOL is much easier.
It turns out that the problem is a known issue with UITextView. My workaround:
#import <UIKit/UIKit.h>
/**
God damn SDK bug means that you can't use editable to enable/disable a
UITextView (It brings up the keyboard when re-enabling)
*/
#interface StupidF_ingTextViewBlocker : UIView {
}
#end
#implementation StupidF_ingTextViewBlocker
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#end
Then in place of the code I've placed above (instead of using editable). To disable (assuming I have an iVar called blocker):
// Put this in a lazy loading property or in loadView
blocker = [[StupidF_ingTextViewBlocker alloc] initWithFrame:writeView.frame];
// The blocker code ~= textView.editable = NO
[self.view addSubview:blocker];
// Removing the blocker ~= textView.editable = YES
[blocker removeFromSuperView];
Subclass UITextView, and implement a single method:
- (BOOL) canBecomeFirstResponder { return NO; }
Use the UITextViewDelegate. You'll need to swap out the delegate object depending on the current state.
If in the blocked state, then you'll use a delegate where textViewShouldBeginEditing returns NO.
the other delegate's textViewShouldBeginEditing would return YES.