UITableViewCell frame isn't updated upon changes - objective-c

I have a custom UITableViewCell to implement swiping horizontally with UIAttachmentBehavior and a UIPanGestureRecognizer. Here's the relevant portion, in the UITableViewCell subclass:
- (void)panDidChange:(UIPanGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:[self tableView]];
location.y = CGRectGetMidY(self.frame);
switch (gesture.state) {
case UIGestureRecognizerStateBegan: {
self.panAttachment = [[UIAttachmentBehavior alloc] initWithItem:self attachedToAnchor:location];
[self.animator addBehavior:self.panAttachment];
break;
}
Then, when UIGestureRecognizerStateChanged, I just set the self.panAttachment.anchorPoint as the location.
Now, this works fine from startup, but as soon as I add a cell to the tableView or delete a cell from the tableView and try to swipe, the cell moves to it's previous position before the tableView change (one cell down if a cell was deleted, one cell up if a cell was added). As far, as I can tell, this is because the cell's frame isn't being updated when it's position in the tableView changes, so location's y coordinate is out of sync. I've tried [self setNeedsDisplay] and all other "Update view" methods I could find, to no avail.
I am creating the animator referenced above like so:
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:[self tableView]];

It looks like the issue is in a couple different places:
Your animator is being initialized with your entire table view as the reference view.
You are then creating the attachment behavior with an anchor of midY in the tableView's coordinate system, meaning your attachment behavior is now anchored to a point in the tableView's reference coordinate system that is wherever the center point of the cell is located. I bet if you tried to scroll your tableView, weird things would also happen.
Depending on what your goal is for your dynamics behavior, you need to modify both your animator's reference frame and attachment's anchor point in order to fully define what physics simulation you are trying to achieve. Based on the above, it looks like you are trying to simply move the entire cell on the pan. I would recommend that you try moving only a specific container view inside your view hierarchy instead since the table view is going to be placing your cells directly using frames.
If you are looking to provide a pan with a snap-back effect on cancel, then this effect might be better implemented using a couple of UIAttachmentBehavior instances. One behavior instance to anchor the view you are wanting to swipe to it's resting position, and one to actually perform the panning and move the view like you are doing above during the pan. You can tweak the interaction between the two interactions by changing the damping and frequency. This question is a good reference for dragging in general using dynamic items.
In summary to fix your current issue:
Define your animator's reference bounds to the cell's coordinate system, preferably using cell.contentView
Have your cell manage the animator directly since the animator should only be concerned with a view in it's coordinate system. Your animator should have no knowledge of the table view's view hierarchy. Add/remove UIDynamicBehavior items from your cell's animator.

Related

Resize Window and contained NSView based on subviews size

For a MacOS app, I have a Window, containing an NSView; into that view, I want to add a subview with a constant size and height.
When loading the subview programmatically by [myView addSubview:mySubview], I want the NSView *myView that is hosting the subview to change in size so it accomodates the subview, and the window to change in size accordingly; so that the edges of the NSView inside that Window keep the same distance to their surroundings in the Window as before. How do I achieve that most efficiently and which properties do I have to specify in IB to make that work? Do I have to adjust the size of myView and of the Window programmatically by hand or can I achieve this in a more beautiful way?
There are multiple ways to achieve this.
A simple one is to set autoresizingMask the value(s) you want.
The mask you can see in Interface Builder are represented by predefined numbers (NSAutoresizingMaskOptions) that you will combine with bit operation
view.autoresizingMask = NSViewMaxXMargin | NSViewMaxYMargin;
which is simmilar to Autoresizing like in this screenshot of IB
The checkmark on Layout Translates Mask Into Constraints has to be made, either in IB or programmatically so they are used as constraints.
view.translatesAutoresizingMaskIntoConstraints = YES;
The relative positioning to its enclosing superview is defined when the view is instanced with -initWithFrame: with the given frame or with the values set in IB when it creates an instance and inits the UI element via -initWithCoder: .
Be aware this does not stop the autolayout mechanism of IB to warn you that your desired coordinates, sizes and constraints are maybe clashing with constraints.
As suggested by #Willeke, I needed to understand and apply Autolayout. To make it work in IB, I set the autoresizingMask of my subview to stick to all for sides and automatically adjust width and height. Even though it can be done completely in IB, I think programmatically this would be
subview.autoresizingMask = NSViewWidthSizable | NSViewHeightSizeable;
As pointed out by #Ol Sen in his answer, Translates Mask Into Constraints also has to be activated.
To arrange the elements inside that subview that is added programmatically as described in the opening post, I rely on nested stackviews and resize them instead of resizing the parent.
The only problem left is to correctly adjust the frame of the subview to match the parent view before adding it. If this step is left out, the contraints the autoresizing mask of the subview is translated into when adding it will result in the correct resizing behaviour, but wrong margins. The essential code looks like this:
MySubViewController *subViewController = [[MySubViewController alloc] init];
subViewController.view.frame = superView.bounds; // Correct the margins
[superView addSubview:subViewController.view];

Using CATransform3D

I'm trying to make a rotation on a tableview to tilt the table (to give the effect of a 3d text crawl similar to the star wars opening crawl).
After looking around I found this question objective-с CALayer UIView real rotation
and the accepted answer seems to do what I want, however when I apply the code to my TableView it does nothing and the table appears as usual.
This is the code I am copying:
float distance = 50;
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0 / -distance;
_tableView.layer.transform = CATransform3DRotate(basicTrans, M_PI_4, 1.0f, 0.0f, 0.0f);
I'm placing this in my viewDidLoad method after creating my array of Strings (that populate the tableView)
I currently only have three other methods in the Controller:
didReceiveMemoryWarning (automatically addd when project created)
tableView: numberOfRowsInSelection (used for setting up the table view)
tableView: cellForRowAtIndexPath (used for setting up the table view and setting the cells text form the array)
My understanding is that the tableview has a CALayer, and that the CATransform3D manipulates this to give the representation of the view in a 3d space. If my understanding is correct then I don't get why the list is shown normally on screen? I appreciate the numbers my not give the effect I want yet but they should at lest effect the appearance of the tableView on screen.
Also I have imported QuartzCore etc and added it in linked frameworks
Solution is to use the code marked as OLD answer in the the - (UITableViewCell *)tableView: cellForRowAtIndexPath: method after the cell is checked for being null.
Since the approach suggested below has not worked, another thing I would try out is applying the transform to the UITableView's subviews. Actually, UITableView is a UIScrollView, so it is just a container for subviews that make up the real content of the table view. I would try something like this:
for (UIView* subview in tableView.subviews) {
subview.layer.transform = ...;
}
I have never inspected a table view subviews hierarchy, so I cannot say whether this will work or you should rather apply the transform to just one of the subviews, but I hope this can lead you in the right direction.
OLD ANSWER:
You could try setting your table view's layer sublayerTransform instead of `transform':
You typically use this property to add perspective and other viewing effects to embedded layers. You add perspective by setting the sublayer transform to the desired projection matrix. The default value of this property is the identity transform.
(source).
I am suggesting this based on the hypothesis that a UITableView has quite a complex structure in terms of subviews, so transforming just the view's layer might have no effect. I haven't tried it, though, so I cannot guarantee it will work.

how to find a object in rect or at point

On a NSView there are many other NSViews and some of them are moving with keyboard scroll keys. when moving object come on top of any static one i want to get which object sits under moving one. with mouse this is easy however without mouse i couldn't find a way to achieve this.
You'll need to loop through all of the views you want to test, get each view's frame, convert that rectangle to the relevant view's coordinate system (search for “convertRect:” in the NSView docs), and then use the geometry functions to test whether the moving view's converted frame intersects the static view's frame.
This might help you
-(void)handleTap:(UIGestureRecognizer *)gesture
{
CGPoint tappedPoint = [gesture locationInView:self.view];
NSLog("You tapped in on screen point : %#",tappedPoint);
}
Using this tappedPoint we can check that in which view's rect these points are present.
Important We have to add tap gesture in our view to use this function.

Cocoa moving overlapping NSViews

I have several views inside one main view. Those views are all behind one another so that only the top one is currently seen. What I would like to do is, upon some particular user input, have one of the views in the back move lower on the screen, so it can be seen below the top view. I've been trying to do it by determining if the two views are currently overlapping, lower the one in back, if they are still overlapping, lower it more, and so on until they no longer overlap and are both entirely visible. Then I'd like for the main view to resize so that both views can be seen but I think that'd just be using setBounds or setFrame, haven't gotten there yet.
For the first problem I haven't found a way to literally see if two views overlap, if that can't be done I thought maybe by drawing rects on the view bounds and checking if those overlap, but I don't get how I can do either of these. I think bounds or frame is needed, but I'm not really sure how these are different.
Basically, I want to check if two NSViews overlap, if they do, I want to lower one of them.
EDIT: This part of the problem is pretty much solved, but now a new problem arose. I have three views, one main view, and two subviews. The main view doesn't have anything, it only contains several subviews, whatever is inside the main view is what should be seen. The two subviews are stacked on top of the main view, so that the frontmost one is the visible one, the one behind it is set to hidden. When the user hits start, the view in the back becomes visible and I set a repeating timer with a selector that will check if the views overlap, like #Vince said, if they do, it offset's the one in the back down a little and checks again until they don't overlap anymore. Using this:
- (void)updateViews {
CGRect viewFrame = [view frame]; //view is the frontmost view that will not move
CGRect backroundViewFrame = [backgroundView frame]; //the view in the back
CGRect rectIntersection = CGRectIntersection (viewFrame,
backgroundFrame);
//if the intersect is NOT null, it'll offset down by 2
if (!CGRectIsNull(rectIntersection)) {
CGRect newFrame = CGRectOffset (
rectIntersection,
0, //doesn't move in x
-2 //lowers by 2 in y
);
[backgroundView setFrame:newFrame]; //lowers the view to the new offset rect
} else{
[viewsUpdater invalidate]; //stops the timer when rects no longer intersect
viewsUpdater = nil;
}
The problem is that when I lower the backgroundView, it's only visible in the space where it intersects with the front view. Even if I expand the main view lower (which is the idea, they will all start one on top of the other with the same dimensions and when the backgroundView lowers, the main view will adjust and display both). But right now it doesn't matter how large the main view is, the backgroundView is only visible in the area where it intersects with the front view. I think it's moving to where it has to, but by the time it stops it's completely invisible, so I can't tell.
Thanks for the help.
Basically, you will be able to know if two NSView objects overlaps by getting the intersection of the two frame properties (CGRectIntersection()) and then testing for the result to be null using CGRectIsNull().
Here is the doc for it.
Now, about putting one view over another, it sounds like you need to use this method from NSView : -(void)setSubviews:(NSArray *)newSubviews;, which according to the documentation, lets you reorder the subviews.
Or simply using -(void)replaceSubview:(NSView *)oldView with:(NSView *)newView;, that said, since the oldView will be released, be careful to retain a reference to it before.
If you just attempted to move a view lower, you only have to change the origin of the reciever's frame, for example :
CGRect actualFrame = [aView frame];
CGPoint framesOrigin = actualFrame.origin;
CGRect newFrame = CGRectMake(framesOrigin.x,
(framesOrigin.y)+50,
actualFrame.width,
actualFrame.height);
[aView setFrame:newFrame];
This will move aView lower, by changing the y coordinate of its frame's origin.
Finally, about the difference between bounds and frame properties, this post on SO Difference between view's frame and view's bound + iPhone, should answer your question ;)

NSTextFieldCell Coordinates

I'm trying to get the NSPoint cooridnates of an NSTextFieldCell, but NSTextFieldCell doesn't inherit from NSView, and therefore doesn't have the frame method. Any ideas on how to get around this?
I'm trying to use Matt Gemmel's MAAttachedWindow to attach little helper popups to certain elements. I want to attach it to an area on the window, and other than hacking it together by manually messing with the x and y coordinates, I'm not sure how to get the coordinates.
You can call [theCell controlView] to get a reference to the control that owns a particular cell. If the NSTextFieldCell object is part of a simple control, such as an NSTextField, the following should be sufficient:
NSRect cellFrame = [[theCell controlView] frame];
NSPoint origin = cellFrame.origin;
//..
If, however, the NSTextFieldCell is part of a more complex control, such as an NSTableView, where a single cell is used in multiple places, you will need more information in order to determine the proper rectangle. NSCell offers the method representedObject, which can help you to determine which object in the NSTableView is represented by the cell at that particular moment. Without knowing more about your specific case, I don't know how much more detail to provide in that regard.
Here is one possible solution, assuming you are able to discern the row and column information from the object stored in representedObject:
NSTableView * tableView = [theCell controlView];
id cellObject = [theCell representedObject];
NSInteger row = //... determine from representedObject
NSInteger col = //... determine from representedObject
NSRect cellFrame = [tableView frameOfCellAtColumn:col row:row];
A cell is a reusable object that's owned by an NSControl (an NSView subclass). It doesn't have an origin point because it doesn't actually represent a place on the screen, it's up to the control (which has a specific frame rectangle) to draw it where and when it's needed. Think about a table view for example, it might use a single cell that's re-drawn in several places for each row.
The cell is passed a frame rectangle by the control when it's drawn onto the screen, in most cases that should be all you need. You can also take advantage of NSCell's sizing methods, which can tell you how much room it needs to display its content.
For using MAAttachedWindow, could you instead use the control's frame (if it's a single-cell control), or in the case of a table view with a specific row one of the NSTableView layout methods? rectOfRow: or rectOfColumn:, for example. You could override NSTextFieldCell and position the window in one of the drawing methods, but I'd save that for my absolute last choice if possible.
It doesn't have co-ordinates. There are none to get. The way cells work is that the view tells the cell “draw here”.
So, if you're not in one of the cell's drawing methods and you really, really need co-ordinates, you need to ask the view that owns the cell. Usually, this is [cell controlView].
(Perhaps you're thinking of UIKit? UITableViewCells are very different from NSCells.)