I have a UITableViewCell with the following hierarchy:
I use swipe and pan gestures to move the Container View to left, so that the trash button is visible, Similar to Instagram:
But, on cell selection The thrash button is visible:
EDIT:
Please Note:
The trash button is actually a custom button with a transparent png image. The background colour of the button is then set as red.
Please provide your valuable suggestions or a solution to this problem. Thanks!
As I didn't get a proper answer, I was forced to fix this issue as pointed out by #hoang Van Ha.
In the custom cell class:
I hid the button by overriding setSelected method as follows:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (self.cellContainerView.frame.origin.x == 0) {
self.trashButton.hidden = YES;
}
}
at the beginning of the gesture methods, i.e. pan and swipe I show the button again and on completing the gesture, I check if the frame of the container view has origin zero. If so, the button should be hidden.
- (IBAction)handleSwipeGesture:(UISwipeGestureRecognizer *)recognizer
{
self.trashButton.hidden = NO;
if ( recognizer.direction == UISwipeGestureRecognizerDirectionRight ) {
[UIView animateWithDuration:0.2
animations:^{
self.cellContainerView.frame = CGRectMake(0, self.cellContainerView.frame.origin.y, 320, self.cellContainerView.bounds.size.height);
}
completion:^(BOOL finished) {
self.trashButton.hidden = YES;
}
];
}
}
In the controller class, in cell for row method, do the same process.
if (cell.cellContainerView.frame.origin.x == 0) {
cell.trashButton.hidden = YES;
}
Please feel free to comment, or even post a better answer!
Related
I have a simple KeyboardAdjuster class that is a property of my form views. If one of the form fields is hidden by the keyboard, then entering that field will have an animation to move the whole frame's origin.y up, so that the field appears above the keyboard. A very common approach. It also has a few complexities like calculating how much to scroll by when navigating between fields, but that's not important right now. . . I've been using this utility class since iOS5.
Example Form:
The problem:
On iOS8 it has simply stopped working.
When animating the frame starts by snapping in the opposite direction exactly the amount that its supposed to scroll by. And then scrolling back to the origin.
I tried commenting the animation part out, and simply setting the frame. No effect.
For example if the frame is supposed to be: {0, -127, 320, 480} then it will simply stay at {0, 0, 320, 480}
Why doesn't this work on iOS8? Has something changed that I've missed?
About the views:
My views are hand-coded, they're a sub-class of a simple form base-view. (Contains keyboard adjuster and a scroll-view). The other elements are added with initial frames of CGRectZero and then laid out manually in layoutSubviews
How the view/controller created:
There is a RootViewController that acts as a container controller (UIView containment). It:
Has a main navigation controller
Has a container to present / dismiss a hamburger menu for the nav controller's top views (these can change).
Has a container to present overlays with a custom bounce animation.
So the view is created as follows:
- (instancetype)initWithView:(INFAcceptGiftView *)view offerDao:(id <INFOfferDao>)offerDao
locationTracker:(INFLocationTracker *)locationTracker
{
self = [super init];
if (self)
{
self.view = view;
_offerDao = offerDao;
_locationTracker = locationTracker;
}
return self;
}
What triggers the keyboard animation?:
The UIView is a sub-class of form base view, which is a UITextFieldDelegate:
interface INFFormBaseView : UIView <UITextFieldDelegate, INFInputAccessoryDelegate>
{
UIResponder *_currentResponder;
INFInputValidator *_validator;
}
When a field is entered:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[_validator dismissMessages];
[_keyboardAdjuster scrollToAccommodateField:textField];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[_keyboardAdjuster scrollToAccommodateField:nil];
}
I have a different solution for you which works on both iOS 7 & 8 and Auto Layout.
In my example I have two UITextFields which I move and hide depending on their position and the position of the keyboard. In this particular case, I switch the UITextFields between them and hide the inactive one.
In viewDidLoad you register for the following notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardShowed:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardHidden:)
name:UIKeyboardWillHideNotification object:nil];
After that you grab a hold of the default frames of your views, in this case the two UITextFields:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
/*** FOR AUTOLAYOUT MODIFICATIONS & ADDITIONS # RUNTIME ***/
self.mailTextFieldDefaultFrame = self.mailTextField.frame;
self.passwordTextFieldDefaultFrame = self.passwordTextField.frame;
}
And when you receive UIKeyboardWillShowNotification you'll start moving your views:
- (void) keyboardShowed:(NSNotification*)notification {
//GET KEYBOARD FRAME
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//CONVERT KEYBOARD FRAME TO MATCH OUR COORDINATE SYSTEM (FOR UPSIDEDOWN ROTATION)
CGRect convertedFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];
if ([self.mailTextField isFirstResponder]) {
[UIView transitionWithView:self.mailTextField
duration:.3f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.mailTextField.alpha = 1.0f;
self.mailTextField.frame = CGRectMake(self.mailTextField.frame.origin.x,
convertedFrame.origin.y -
self.mailTextField.frame.size.height - 25,
self.mailTextField.frame.size.width,
self.mailTextField.frame.size.height);
self.passwordTextField.alpha = 0.0f;
}
completion:nil];
} else if ([self.passwordTextField isFirstResponder]) {
[UIView transitionWithView:self.passwordTextField
duration:.3f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.passwordTextField.alpha = 1.0f;
self.mailTextField.frame = self.passwordTextField.frame;
self.passwordTextField.frame = CGRectMake(self.passwordTextField.frame.origin.x,
convertedFrame.origin.y -
self.passwordTextField.frame.size.height - 25,
self.passwordTextField.frame.size.width,
self.passwordTextField.frame.size.height);
self.mailTextField.alpha = 0.0f;
}
completion:(void (^)(BOOL finished)) ^{
}];
}
And when you hide the keyboard:
- (void) keyboardHidden:(NSNotification*)notification {
//RESTORE ORIGINAL STATE OF TEXTFIELDS
[UIView transitionWithView:self.view
duration:.3f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.mailTextField.frame = self.mailTextFieldDefaultFrame;
self.passwordTextField.frame = self.passwordTextFieldDefaultFrame;
self.mailTextField.alpha = 1.0f;
self.passwordTextField.alpha = 1.0f;
}
completion:nil];
}
Here's the solution, posting in case it helps someone.
I mentioned above that I'm using UIView containment, so I have a root view controller that:
Contains a UINavigationController (the root view is replaceable).
Contains a Menu Controller (dealloc'd when not in use)
Presents an overlay with custom animation
My root view had layout subviews as follows:
- (void)layoutSubviews
{
[super layoutSubviews];
[_mainContentViewContainer setFrame:self.bounds];
}
This behaved the way that I wanted it to pre iOS8, but not afterwards. Technically it appears that iOS8 is doing the right thing - I should only be laying out the _mainConentViewContainer on startup or orientation change.
I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.
I am creating a UITableView with custom UITableViewCells. iOS 7's new delete button is causing some problems with the layout of my cell.
If I use the "Edit" button, which makes the red circles appear I get the problem, but if I swipe a single cell it looks perfect.
This is when the Edit button is used:
[self.tableView setEditing:!self.tableView.editing animated:YES];
This is when I swipe a single cell:
As you can se my labels overlaps the delete button in the first example. Why does it do this and how can I fix it?
try using the accessoryView and editingAccessoryView properties of your UITableViewCell, instead of adding the view yourself.
If you want the same indicator displayed in both editing and none-editing mode, try setting both view properties to point at the same view in your uiTableViewCell like:
self.accessoryView = self.imgPushEnabled;
self.editingAccessoryView = self.imgPushEnabled;
There seems to be a glitch in the table editing animation in IOS7, giving an overlap of the delete button and the accessoryView when switching back to non-editing state. This seems to happen when the accesoryView is specified and the editingAccessoryView is nil.
A workaround for this glitch, seems to be specifying an invisible editingAccessoryView like:
self.editingAccessoryView =[[UIView alloc] init];
self.editingAccessoryView.backgroundColor = [UIColor clearColor];
The problem is that in edit mode the cell's contentView changes in size. So either you have to override layoutSubviews in your cell and support the different frame sizes
- (void) layoutSubviews
{
[super layoutSubviews];
CGRect contentFrame = self.contentView.frame;
// adjust to the contentView frame
...
}
or you take the bait and switch to autolayout.
First I thought setting contentView.clipsToBounds to YES could be an ugly workaround but that does not seem to work.
I've resolved this problem with set up constraints without width only leading and trailing
As tcurdt mentioned, you could switch to autolayout to solve this issue. But, if you (understandably) don't want to mess with autolayout just for this one instance, you can set the autoresizingMask and have that turned automatically into the appropriate autolayout constraints.
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
Just use this method in your custom TableViewCell class you can get the perfect answer,
Here self is UITableviewCell
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
for (UIView *subview2 in subview.subviews) {
if ([NSStringFromClass([subview2 class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"]) { // move delete confirmation view
[subview bringSubviewToFront:subview2];
}
}
}
}
And if any one want to adjust the Delete Button Size, Use the following Code
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
for (UIView *subview2 in subview.subviews) {
if ([NSStringFromClass([subview2 class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"]) { // move delete confirmation view
CGRect rect = subview2.frame;
rect.size.height = 47; //adjusting the view height
subview2.frame = rect;
for (UIButton *btn in [subview2 subviews]) {
if ([NSStringFromClass([btn class]) isEqualToString:#"UITableViewCellDeleteConfirmationButton"]) { // adjusting the Button height
rect = btn.frame;
rect.size.height = CGRectGetHeight(subview2.frame);
btn.frame = rect;
break;
}
}
[subview bringSubviewToFront:subview2];
}
}
}
}
Best way to remove this problem is that add an image in cell and set it in Backside.
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bgImg.png"]];
imageView.frame = CGRectMake(0, 0, 320, yourCustomCell.frame.size.height);
[yourCustomCell addSubview:imageView];
[yourCustomCell sendSubviewToBack:imageView];
If your text would overlap the delete button then implement Autolayout. It'll manage it in better way.
One more case can be generate that is cellSelectionStyle would highlight with default color. You can set highlight color as follows
yourCustomCell.selectionStyle = UITableViewCellSelectionStyleNone;
Set your table cell's selection style to UITableViewCellSelectionStyleNone. This will remove the blue background highlighting or other. Then, to make the text label or contentview highlighting work the way you want, use this method in yourCustomCell.m class.
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted)
self.contentView.backgroundColor = [UIColor greenColor];
else
self.contentView.backgroundColor = [UIColor clearColor];
}
I hope you understand it in a better way.
Bringing to front UITableViewCellDeleteConfirmationView in the layoutSubviews of the custom cell works for me on iPhone, but not on iPad.
I have a UITableView in the master part of a splitViewController for the iPad, and in this case
the frame of the UITableViewCellDeleteConfirmationView is (768 0; 89 44), instead of (320 0; 89 44)
So I resize the frame in the layoutSubviews method and this works for me
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews)
{
for (UIView *subview2 in subview.subviews)
{
if ([NSStringFromClass([subview2 class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"])
{
CGRect frame = subview2.frame;
frame.origin.x = 320;
subview2.frame = frame;
[subview bringSubviewToFront:subview2];
}
}
}
}
If you are putting content in the UITableViewCell's contentView, be sure you use self.contentView.frame.size.width and not self.frame.size.width in layoutSubviews.
self.frame expands width in editing mode, and will cause any content on the right to extend past the bounds of the contentView. self.contentView.frame stays at the correct width (and is what you should be using).
Try this: Might be you are setting cell setBackgroundImage in cellForRowAtIndexPath (Delegate Method). Do not set this here. Set your image in:
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { cell.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"cellList.png"]]; }
Enjoy Coding.
My solution is to move whole contentView to the left when Delete button showing:
override func layoutSubviews() {
super.layoutSubviews()
if editingStyle == UITableViewCellEditingStyle.Delete {
var rect = contentView.frame
rect.origin.x = self.showingDeleteConfirmation ? -15 : 38
contentView.frame = rect
}
}
Like the iBooks app, when you pull down the tableview, a search bar and segmented control appear, to allow you to search and switch between two types of views.
It sticks in that position when you pull down far enough, and alternatively, gets hidden when you pull the tableview up enough.
I am trying to implement the same thing with a UISegmentedControl.
So far I have added a segmented control successfully as a subview to the table. (It has a negative Y frame so make it stick above the tableview).
I have also implemented this code:
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
float yOffset = scrollView.contentOffset.y;
if (yOffset < -70) {
[scrollView setContentOffset:CGPointMake(0.0f, -70.0f) animated:YES];
} else if (yOffset > -10) {
[scrollView setContentOffset:CGPointMake(0.0f, -11.0f) animated:YES];
}
}
This works great, until I try using the segmented control. Where the table will just act like it is scrolling, ignoring the segmented control altogether (i.e. if I tap on a segment, it doesn't even get selected, instead the table scrolls up, hiding the segmented control.
I did use the scrollViewDidScroll method but this made it buggy and the scrolling jumpy.
I also tried to make the segmented control's exclusiveTouch = YES, but this had no effect whatsoever.
I would be thankful for all help! thanks in advance!
Here is my code which works:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Discover top
//
CGFloat topY = scrollView.contentOffset.y + scrollView.contentInset.top;
if (topY <= self.tableHeaderHeightConstraint.constant) {
[self setIsScrolledToTop:YES];
} else {
[self setIsScrolledToTop:NO];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Toggle favourite category
//
if ([self isScrolledToTop]) {
//
// Show
//
} else {
//
// Hide
//
}
}
}
Edited the above code to make it a bit more generic, but syntactically its correct
I am developing an application that allows the user at a certain point to drag and drop 10 images around. So there are 10 images, and if he/she drags one image onto another, these two are swapped.
A screenshot of how this looks like:
So when the user drags one photo I want it to reduce its opacity and give the user a draggable image on his finger which disappears again if he drops it outside of any image.
The way I have developed this is the following. I have set a UIPanGesture for these UIImageViews as:
for (UIImageView *imgView in editPhotosView.subviews) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(photoDragged:)];
[imgView addGestureRecognizer:panGesture];
imgView.userInteractionEnabled = YES;
[panGesture release];
}
Then my photoDragged: method:
- (void)photoDragged:(UIPanGestureRecognizer *)gesture {
UIView *view = gesture.view;
if ([view isKindOfClass:[UIImageView class]]) {
UIImageView *imgView = (UIImageView *)view;
if (gesture.state == UIGestureRecognizerStateBegan) {
imgView.alpha = 0.5;
UIImageView *newView = [[UIImageView alloc] initWithFrame:imgView.frame];
newView.image = imgView.image;
newView.tag = imgView.tag;
newView.backgroundColor = imgView.backgroundColor;
newView.gestureRecognizers = imgView.gestureRecognizers;
[editPhotosView addSubview:newView];
[newView release];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:view.superview];
[view setCenter:CGPointMake(view.center.x + translation.x, view.center.y + translation.y)];
[gesture setTranslation:CGPointZero inView:view.superview];
}
else { ... }
}
}
Thus as you see I add a new UIImageView with 0.5 opacity on the same spot as the original image when the user starts dragging it. So the user is dragging the original image around. But what I want to do is to copy the original image when the user drags it and create a "draggable" image and pass that to the user to drag around.
But to do that I have to pass the user touch on to the newly created "draggable" UIImageView. While it's actually set to the original image (the one the user touches when he starts dragging).
So my question is: How do I pass the user's touch to another element?.
I hope that makes sense. Thanks for your help!
Well, you can pass the UIPanGestureRecognizer object to another object by creating a method in your other object which takes the gesture recognizer as a parameter.
- (void)myMethod:(UIPanGestureRecognizer *)gesture
{
// Do stuff
}
And call from your current gesture recognizer using....
[myOtherObject myMethod:gesture];
Not entirely sure I'm understanding your question here fully. :-/
Maybe:
[otherObject sendActionsForControlEvents:UIControlEventTouchUpInside];
Or any other UIControlEvent
In the end I decided to indeed drag the original image and leave a copy at the original place.
I solved the issue with the gesture recognizers I was having by re-creating them and assigning them to the "copy", just like PragmaOnce suggested.