I have a UITableView with static custom UITableViewCells, I need to scroll a text field into view as currently they are getting hidden behind the keyboard after the return key is pressed and the next responder is set. I know i need to use scrollToRowAtIndexPath:atScrollPosition:animated:. How would I go about scrolling to one of the text boxes?
I know this should scroll itself but if your UIViewController derives from UITableViewController (as Apple states it should be) then the UITableViewController class handles this behaviour for you by implementing UITextFieldDelegate, UIScrollViewDelegate etc. My application stopped doing this as I changed to derive from UIViewController and add the table view on top of the view controller's UIView. So basically I'm missing the features from UITableViewController because I (for other reasons) choose to derive from UIViewControler.
I do this all the time. The way that I do it is I have a method that is called in the UIControlEventEditingDidBegin for the textfield, and in that method, I do:
-(void)startEdit:(UITextField *)textField {
self.prevOffset = self.tableView.contentOffset.y; //I like storing the current offset so I can restore it when the text stops editing, you don't have to do this.
int offSet = [textField superview].frame.origin.y; //this gets the y coordinate of the cell the textField is in. If the table is not at 0,0, you also need to add [[textField superview] superview].frame.origin.y;
offSet-=(self.view.frame.size.height-KEYBOARD_HEIGHT)/2; //where KEYBOARD_HEIGHT is 216 in portrait and 160 in landscape;
if (offSet<0) offSet = 0;
[UIView animateWithDuration:0.3 animations:^{
[self.tableView setContentOffset:CGPointMake(0,offSet)];}];
}
I do a lot of other things as well, but I believe they are specific for my application.
First, if the offset is greater than 0, I set teh contentInset to UIEdgeInsetsMake(0,0,KEYBOARD_HEIGHT,0) because I was having some jumpy scrollViews before I did that.
Also, if the original offset (self.prevOffset) plus the frame's height is greater than the content size (which would also cause jumping as it sets the offset too low then jumps back), I set the prevOffset to MAX(0,contentSize.height-frame.size.height).
These things aren't neccessary, but you are getting Scroll/TableViews that are jumping around, try them out.
UITableViewController has the delegate protocols already in the header field. Since your class is no longer a UITableViewController, you need to manually add the delegate protocol headers for a UITableView to your .h file.
When I create a custom view controller that has a UITableView, I start off with a UITableViewController too, just to get the delegate methods, but then I change UITableViewController to UIViewController as you have done, and manually add the Delegate protocols to the header.
If you want, you can look at UITableViewController.h and copy over the delegate protocols.
For your reference they are:
<UITableViewDelegate, UITableViewDataSource>
So your .h file should look similar to this:
#interface MyTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
Also, don't forget to set the delegates of the controller to the file's owner in Interface builder, or to self in code.
You may also find it a lot easier to use a framework such as the freely available Sensible TableView framework. These frameworks usually provide all the data entry cells out of the box, and will take care of all scrolling/resizing chores on your behalf.
Related
I am trying to emulate Apple's weather app animation where when you scroll, the top header collapses and remains as a condensed sticky cell. I am attempting to do this with all of the UI being done programmatically and without storyboards.
The two ways I've thought of implementing this each have an issue I have not been able to solve.
My initial attempt was having a containerVC contain a UIView (as a header) and a UICollectionViewController. The issue is that The containerVC cannot access the UICollectionViews scrollViewDidScroll() which I would use to calculate and adjsut the size of the UIView. I could make the containerVC the collectionViews delegate but I wanted to avoid that to keep my logic separated. I also tried using Key Value observers but I could not figure out how to make it work.
My second attempt was to use a UICollectionReusableView as a header cell, that way there is no container view, just a single collectionViewController. The issue here is I can't figure out how to dynamically resize the headercell. The header size is currently being returned from referenceSizeForHeaderInSection and I have been unable to find another way of updating this.
Is there a better way to be going about this? Or an easier solution to the issues described that I haven't tried yet?
You can implement your own delegates:
protocol ParentDelegate: class {
func childDidScroll()
}
extension ParentViewController: ParentDelegate {
func childDidScroll() {
print("My child controller did scroll")
}
}
Create a delegate variable in the class that you want to call it:
weak var delegate: ParentDelegate?
Set it to your parent ViewController:
delegate = parentVC
then inside your child scroll method you call it:
delegate?.childDidScroll()
I currently use a UITableViewController (PFQueryTableViewController), I would like to display a UIView over the top of the TableView.
Ideally I'd like to do this in the storyboard, so I can easily add additional Labels/buttons to it. However, I can't seem to figure out how or if you can do it in storyboard. Is that right?
I have tried it programmatically. I first created the variable at the top:
var filterLabelView:UIView = UIView()
I then added the following in ViewDidLoad :
filterLabelView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height/2, width: self.view.frame.width, height: 40)
filterLabelView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
filterLabelView.backgroundColor = UIColor.redColor()
self.view.addSubview(filterLabelView) // See below
I also tried:
self.view.insertSubview(filterLabelView, aboveSubview: tableView)
This creates the red UIView, but it seems to be embedded to the tableview, as when I scroll up and down, the View moves with it.
What I want to create
Ideally, I want the view to sit at the bottom of the screen, and not move when the user scrolls. An example screenshot of how I want it look is below:
I have read that the best way is to use a UIViewController with a UITableView inside it, but I'd rather not take that approach seeing how much my app is already built up.
Can anyone help me create this look? Thanks in advance
You have to derive from UIViewController to get full layout control.
Then simply add a UITableView instance in Storyboard. Constrain it normally, edge-flush, top-flush, and have customView.top = tableView.bottom. Make a normal outlet to your controller.
You just need to remember to make your custom UIViewController adopt the usual dataSource and delegate protocols, as well as assigning itself as those roles to the properties of the UITableView on initialization (usually viewDidLoad()).
There's one more finesse related to clearing the selected cell when on viewDidAppear(), but there's nothing else special about a UITableViewController -- it's just a UIViewController with a built-in tableView property and automatically assigned delegates and a very inflexible layout.
ADDENDUM based on comment about how to get around this: It's sort of like asking if there is any way to make a screwdriver drive a nail into wood. Just use the right tool. The top-level view property of a UITableViewController is the UITableView itself. See below, a stock UITableViewController with nothing else, zero code, in layout debug mode.
This means the entire tree of views is embedded in a scrolled entity, so all subviews will scroll with it.
It really shouldn't be that big a deal to re-do this one VC in Storyboard. After all, your rendering code in cellForRowAtIndexPath and your dataSource and delegate generally, don't change at all.
ADDENDUM 2:
Not knowing Parse intimately, this may not be possible. (!! Thanks, Parse.) If Parse doesn't supply you with a separable PFQueryTableView that you can embed yourself, the following workaround may be possible, but you'll need to understand the Objective-C code:
https://www.parse.com/questions/pfquery-tableview-for-uiviewcontroller
Let's call this UIView subclass - SomeClass. This SomeClass is a part of a static library. Some customer will use this library and will add instances of this SomeClass to the cells of his (customer's) table view.
I (SomeClass) need to determine when the SomeClass "enters" screen (will become visible), and when will "exit" screen (will become non-visible).
I can use didMoveToWindow: method and then check self.window for nil. BUT, there is a problem, SomeClass gets this event, before it is actually visible, because of cells "preparation" by table view concept. And I need to know for sure, it is 100% visible by some user.
One way to determine is by using scrollViewDidScroll:. Suppose SomeClass will get scroll view by using iteration on super views and will subscribe as a delegate to found scroll view. But he will be removed by some cell that will subscribe itself as a delegate to scroll view. So I need to invent here some solution for this. For example, in Android, there is possibility to add observer, in that case SomeClass is always a listener and is not overriding any other listener. There is many to one relation in Android, not like in iOS, one to one.
The other way, I can enable some timer in didMoveToWindow: when SomeClass becomes visible, that will check each X time, its frame. The timer will be disabled, when SomeClass will go from screen.
Probably there is a way to check at low level, without using scroll view and timer on some low-level redraw method. Is it possible?
So what is the best (will use less resources / good design) method?
You can use CGRectIntersectsRect to check if the cell's frame intersects with the frame of your custom view. Aside from that, didMoveToWindow is the method you are looking for.
If as you say the table view cell will always have SomeClass as a subview, then it would make more sense to use UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:.
I want to set the tag of a UITableViewCell that I, personally, create, with initWithStyle:reuseIdentifier: in tableView:cellForRowAtIndexPath:, to 0 or 1 to indicate whether I should position its subviews to the left or right. Is it safe to do this, or will Apple mess with the value of this cell's tag? I know I could use a separate cell identifier for each type of cell, but both cells are pretty much the same, besides positioning of subviews and some colors. (I'm recreating the native Messages app with the chat bubbles.)
While you can certainly use tags, I'd definitely suggest to be more explicit. You will do yourself a favor when you look at your code a few months from now. I'd create a custom UITableViewCell subclass that has a contentAlignment property that you can explicitly set to left or right. It will be much more readable that (ab)using the cell's tag.
Something like this:
typedef NS_ENUM(NSUInteger, MyTableViewCellContentAlignment) {
MyTableViewCellContentAlignmentLeft,
MyTableViewCellContentAlignmentRight
};
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) enum MyTableViewCellContentAlignment contentAlignment;
#end
Enjoy.
It's okay, but a better place to set the tag would be the tableView:willDisplayCell:forRowAtIndexPath: method of the UITableViewDelegate. According to the docs:
A table view sends this message to its delegate just before it uses cell to draw a row, thereby permitting the delegate to customize the cell object before it is displayed. This method gives the delegate a chance to override state-based properties set earlier by the table view, such as selection and background color. After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.
It is absolutely safe to set a UITableViewCell's (or any UIView's) tag to whatever value you want. No one (including Apple) will mess with this tag's value outside the control of your application.
I think it's OK to give UITableViewCell a tag. Interface Builder lets you do it. Still, feeling shaky about doing so is understandable. Normally, you'd give a tag to a view that you're explicitly adding to another view via addSubview:. But, think about it. If Apple messed with the values of any of the tags of the views within your view controller's view, then that could break the functionality of something like [self.view viewWithTag:TEXT_FIELD_TAG]. So, I doubt they would. I have heard once that Apple reserves the tag integers <= 10, and therefore, you should use integers > 10 for tag values, but I don't see how that could be true.
I have a View that has a UIScrollView, with some text (loaded via JSON). I need to detect when the user has reached the end of the scroll (I have seen other questions about it here) but the problem is, i have done the .h implement UIScrollViewDelegate, I have
#property (retain, nonatomic) IBOutlet UIScrollView *scroll;
in my .h file, and synthezised it in the .m file. I have this in my viewDidLoad:
self.scroll=[[UIScrollView alloc]init];
self.scroll.delegate=self;
Connections are made in the Interface Builder (the IBOutlet "scroll" with the UIScrollView, and so on).
After that, i fulfill the UITextView with the call to the JSON, and do other stuff not related to the scrollView. I have, then, implemented the method scrollViewDidScroll. But, it is never called. The scroll works fine, lets me go up and down to see the whole text. But, any idea why the method of the protocol is not called?
Thank you.
You don't need to alloc and init a new UIScrollView instance. When you link it from the IB it is already done. That way you are breaking the link.
By the way, your scroll property is defined as retain, so
self.scroll = [[UIScrollView alloc]init];
is a memory leak.
Does your UIViewControllercomplies with the UISrollViewDelegate? The protocol here.
Keep in mind the following:
The methods declared by the UIScrollViewDelegate protocol allow the
adopting delegate to respond to messages from the UIScrollView class
and thus respond to, and in some affect, operations such as scrolling,
zooming, deceleration of scrolled content, and scrolling animations.
There are not mandatory methods, but you need to adopt the protocol in order to use the methods.
That is... I MUST set programatically the contentSize (despite the scroll is done nicely) if you want the method scrollViewDidScroll get called.
Fantastic... x(