Implementing UIScrollView Methods when Subclassing UITextView - ios

I'm having problems controlling the scrolling within a UITextView that I'm using, so I've opted to create my own subclass.
I've got a very basic question about providing implementations for some of the UIScrollView superclass methods.
Here's my skeleton code for the UITextView subclass:
#interface PastedTextView : UITextView
#end
#implementation PastedTextView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"scrollRectToVisible");
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
NSLog(#"setContentOffset");
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"zoomToRect");
}
#end
When will those UIScrollView methods be called? Only from my own client code? Or will they be called by the framework?
Update:
The reason I've asked this is because I'm having the following problem: I'm programatically adding text to the UITextView (from the pasteboard). When I do so, if the textview has scrolled such that the top of the content is no longer in view, the text view scrolls back to the top after the new text has been appended.
I'm not explicitly triggering this scroll, so it's happening within the framework.
I haven't found anything in Apple's documentation that describes this behaviour. So, I've been trying to locate the source of the scrolling so that I can avoid it...
When this scroll happens, none of the above methods are called. Incidentally, neither is UITextViews scrollRangeToVisible method (I've tried adding that method to the subclass implementation). I can't figure out why that implicit scroll back to the top is happening and I want to prevent it...

If you override these UIScrollView methods as shown, any caller (be it your code, or the system's) will hit your implementation instead of the builtin UIScrollView one. If you want to take advantage of the system implementation, you can always call super.
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[super zoomToRect:rect animated:animated];
NSLog(#"zoomToRect");
}

Related

How do Prevent a copy/paste/select popover on a UITextView using UIMenuController iOS5.1

The question: How do I prevent the copy/paste/select popup that occurs over a UITextView from appearing (not using UIwebView and css)?
I did not want to go the rout of UIWebView as some posts have gone because I already am using UIViews with UITextFields for data entry. I had tried unsuccessfully to implement the solutions dealing with UITextField in my implementation file of my view controller with the methods: targetForAction:withSender, setMenuVisible:animated and finally canPerformAction:withSender. (It NO WORKY WORKY - [sad face])
Ok, I found a working solution (in Xcode 5.1) to my question which, in short, is subclassing the UITextField.
I realized I wasn't really overriding the default behavior of the UITextField in the view controller like I wanted to and neither was putting the methods listed here override the behavior of the textfield delegate in the view controller file. The Key was to subclass the UITextField itself with -targetForAction:withSender. (I know some of you are screaming at the screen about how OBVIOUS that was!) It was not obvious to me. Like most problems when first figuring them out I went through a lot of different paths some I found here in SO. But the solution is a simple one. I want to share this solution in its own area so hopefully it can help someone out.
The header file:
//
//
#import <UIKit/UIKit.h>
#interface TPTextField : UITextField
- (id)targetForAction:(SEL)action withSender:(id)sender;
#end
and the implementation file (.m)
//
//
#import "TPTextField.h"
#implementation TPTextField
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#pragma mark - method overrides - deny user copy/paste on UITTextFields
- (id)targetForAction:(SEL)action withSender:(id)sender
{
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (action == #selector(selectAll:) || action == #selector(paste:) ||action == #selector(copy:) || action == #selector(cut:)) {
if (menuController) {
[UIMenuController sharedMenuController].menuVisible = NO;
}
return nil;
}
return [super targetForAction:action withSender:sender];
}
#end
In your storyboard or nib/xib file just connect this class to your UITextfield like the picture below:
I have it on git to for easy access here. Please let me know if this is helpful to you!
Tony
If the UITextView is created as an object on a storyboard, the solution is even easier. In Attributes Inspector for the UITextView object, under Behavior, uncheck Editable and uncheck Selectable. Under the Scroll View section, you can check Scrolling Enabled if you want the user to be able to scroll text.

Using UICollectionView scrollToItemAtIndexPath to return to last selected cell

I have UICollectionView for the user to select images from a grid. Once a cell is selected, the view controller is released, including the UICollectionView.
I'd like to remember where the user was last time they used the UICollectionView and automatically scroll to that location when the view controller is loaded again.
The problem I'm encountering is when this can be done. I'm assuming I need to wait until the UICollectionView has been fully loaded before executing scrollToItemAtIndexPath:atScrollPosition:animated:.
What's the preferred way to determine when the view controller and UICollectionView are fully laid out?
I spent some time exploring solutions. #Leo, I was not able to scroll in viewDidLoad. However, I was able to achieve good results keeping track of the state of the view life cycle.
I created a constant to remember the content offset. I used a constant so it would retain it's value between loads of the VC. I also created a property to flag when it was okay to scroll:
static CGPoint kLastContentOffset;
#property (nonatomic) BOOL autoScroll;
#property (weak, nonatomic) IBOutlet UICollectionView *collection;
The life cycle code I used:
- (void)viewDidLoad
{
[super viewDidLoad];
self.autoScroll = NO; // before CollectionView laid out
}
- (void)viewDidDisappear:(BOOL)animated
{
kLastContentOffset = self.collection.contentOffset;
[super viewDidDisappear:animated];
}
- (void)viewDidLayoutSubviews
{
if (self.autoScroll) { // after CollectionView laid out
self.autoScroll = NO; // don't autoScroll this instantiation of the VC again
if (kLastContentOffset.y) {
[self.collection setContentOffset:kLastContentOffset];
}
}
During the layout of the collectionView I set the flag indicating that the next viewDidLayoutSubviews should auto scroll:
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
self.autoScroll = YES;
// return count here
}
The important part was saving the content offset when my view disappeared. The constant is not remembered between launches of the application which what I wanted and the reason for not saving it in preferences.
Seems like there has to be a more elegant solution but this works well.

When can I start using properties set using UIAppearance?

I have some custom appearance properties in my view class (a descendant of UIView). I want to customize the view appearance according to these properties, but I can’t do that inside the initializer, since the values set using [[MyClass appearance] setFoo:…] aren’t in effect at that point:
#interface View : UIView
#property(strong) UIColor *someColor UI_APPEARANCE_SELECTOR;
#end
#implementation View
#synthesize someColor;
// Somewhere in other code before the initializer is called:
// [[View appearance] setSomeColor:[UIColor blackColor]];
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", someColor); // nil
return self;
}
#end
They are already set in layoutSubviews, but that’s not a good point to perform the view customizations, since some customizations may trigger layoutSubviews again, leading to an endless loop.
So, what’s a good point to perform the customizations? Or is there a way to trigger the code that applies the appearance values?
One possible workaround is to grab the value directly from the proxy:
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", [[View appearance] someColor); // not nil
return self;
}
Of course this kills the option to vary the appearance according to the view container and is generally ugly. Second option I found is to perform the customizations in the setter:
- (void) setSomeColor: (UIColor*) newColor
{
someColor = newColor;
// do whatever is needed
}
Still I’d rather have some hook that gets called after the appearance properties are set.
Why not wait until
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview) {
... code here ...
}
}
if it's giving you trouble?
I believe UIAppearance properties are applied to a view when it is being added into a view hierarchy. So presumably you could access the set properties in UIView didMoveToSuperview.
Caveat: I am using Swift 2, so not sure about earlier versions of Swift / Objective-C. But I have found that didMoveToSuperview() will not work. The properties are available in layoutSubviews(), but that's not a great place to do anything like this (since it can be called more than once). The best place to access these properties in the lifeCycle of the view I have found is didMoveToWindow().
I would have thought that viewDidLoad would be best if it's a one-time thing. Otherwise, viewWillAppear.
EDIT:
If you want to do it in the view, and not it's controller then I would create a custom init for the view along the lines of:
-(id) initWithFrame:(CGRect) frame andAppearanceColor:(UIColor)theColor;
thereby passing the colour into the view at creation time.

How to determine if a UITableViewCell is being dragged?

I would like to know if a cell is being dragged (via the grip on the right side when a UITableView is in editing mode). I would like to change the background while it is in that state.
There is no callback unfortunately, but since the default behaviour is to adjust the 'alpha' value of the UITableViewCell you can check for that in '- (void)layoutSubview' of your UITableViewCell subclass.
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.alpha < 0.99) {
// we are most likely being dragged
} else {
// restore for not being dragged
}
}
Note that this is a bit of a hack. I noticed that this doesn't work when the table view's separatorStyle is set to 'UITableViewCellSeparatorStyleNone'.
I don't know if this is possible in the framework. But if it's not, you can try to override touchedMoved method in an UITableViewCell subclass?
You might then be able to also retrieve the state of your tableView (editing or not) in your UItableViewCell's custom subclass.
It is possible, yes!
In ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
IBOutlet UITableView * myTable;
IBOutlet UIScrollView * myScroll;
}
In ViewController.m
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myScroll = myTable;
myScroll.delegate = self;
}
#pragma Mark UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"%f",scrollView.contentOffset.y);
}
That is it.
It's possible yes! But you have to keep track on the states yourself in the view controller. Define a member
BOOL tableDrag = NO;
In the method
scrollViewWillBeginDragging
you set tableDrag = YES; And in the method
scrollViewDidEndDragging
you set tableDraw = NO;
Now in the scrollViewDidScroll delegate method you can set the background.
If you have several scrollers then tag the scroll view so that you know which one is dragging.
Hope it helps!

How to initialize UITableView? Both from XIB and programmatically?

I have a simple request that I have spent much time on (embarrassingly)..
I have sub-classed a UITableView to add some functionality. These new features require things like NSMutableSet which require allocation/initialization.
I have put my object's initialization routine in
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
which I understood from the apple docs to correct - but this doesn't get called (determined by break-pointing on the code).
I am using IB, and have dragged a UITableView onto the view, and changed it's class to my new sub-class. There is no UITableViewController.
I have also tried:
- (void)loadView {
- (id)init {
- (id)initWithFrame:(CGRect)frame {
with no success. I would like to have this class work both with IB, and programmatically in the future. Everything works apart from the location of this initialization..
When objects load from a XIB file, they get -(id)initWithCoder:(NSCoder*)coder.
If you create objects from XIBs and programmatically, you'll need to implement both the designated initializer -initWithFrame:style: and -initWithCoder:, doing all your init stuff in each one.
Keeping those two in sync can be a pain, so most folks like to break the init stuff out into a private method, typically called -commonInit.
You can see an example of this in action in some of the Apple sample code: HeadsUpUI.
- (void)commonInit
{
self.userInteractionEnabled = YES;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self commonInit];
}
return self;
}
One common mistake that people make when they're new to Cocoa or Cocoa Touch, is to subclass when they don't actually need to. I've seen many examples of custom windows, tableviews, scrollviews and imageviews that need never have been written.
What functionality are you adding to UITableView? Are you sure that what you want to do can't be accomplished through the delegate methods, or by using a custom cell class?

Resources