I have an NSTableView that I want to have a nice looking view over when it's empty. It looks like this:
I have added the TableView inside a custom view (_superViewPackages) with the same size, and then added this code to the numberOfRowsInTableView function to show/hide the overlay:
if ( count == 0 && [[[_superViewPackages subviews] firstObject] isNotEqualTo:_viewOverlayPackages] ) {
[_superViewPackages addSubview:_viewOverlayPackages positioned:NSWindowAbove relativeTo:nil];
[_viewOverlayPackages setTranslatesAutoresizingMaskIntoConstraints:NO];
NSArray *constraintsArray;
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:#"|-1-[_viewOverlayPackages]-1-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(_viewOverlayPackages)];
[_superViewPackages addConstraints:constraintsArray];
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-1-[_viewOverlayPackages]-1-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(_viewOverlayPackages)];
[_superViewPackages addConstraints:constraintsArray];
} else if ( count > 0 && [[_superViewPackages subviews] containsObject:_viewOverlayPackages] ) {
[_viewOverlayPackages removeFromSuperview];
}
This works, but when I remove the last item in the tableView, it won't update until I either click on another tab and back again, or on another application and back again and the application redraws everything.
I have tried a number of combinations of setNeedsDisplay, setNeedsLayout and setNeedsUpdateConstraints on multiple different views but none have worked. I have also moved the updating to its own method and is invoking it with performSelectorOnMainThread to get around any threading problem.
I'm fairly new to Objective-C, so this might be obvious to someone with more experience, but after reading and testing I still can't find what really needs to be called to update.
Right now I have included this code after adding the last constraint:
[self performSelectorOnMainThread:#selector(updateView:) withObject:_viewOverlayPackages waitUntilDone:NO];
In the withObject arg I have tried adding all views I can think of but none have worked.
- (void)updateView:(NSView *)view {
NSLog(#"updateView %#", view);
[view setNeedsUpdateConstraints:YES];
[view updateConstraintsForSubtreeIfNeeded];
[view setNeedsLayout:YES];
[view layoutSubtreeIfNeeded];
[view setNeedsDisplay:YES];
[view display];
}
EDIT: Adding screenshots for clarification.
When the table view contains items, it looks like this:
But, when I remove the last item, and the TableView becomes empty, I want it to look like the screenshot at the top of the post. Instead, it looks like this until I click on another tab and back, or on another application and back etc. Then it updates correctly.
So as i can tell, the view gets added correctly, and I have checked that, It's just that the application doesn't redraw correctly until do something that makes it redraw everything (is my guess).
Related
I am trying to open a new view on select of a table cell in a previous view. The new view that I am trying to open, consists of different sub-views or modules. Hence, I populate each sub-view one by one inside in [self populate] method which is in triggered inside the viewDidLoad method.
-(void)viewDidLoad{
[super viewDidLoad];
[self populate];
}
-(void) populate{
[self.edgeGallery loadImagesWithURLs: _items];
// Modular view: main info
[self.vwListingMainView setListing: _listing];
[self.vwListingMainView refresh];
// Modular view: listing agents
_vwListingAgentsView.agentsArray = _listing.agents;
// Modular view: listing info
_vwListingInfoView.listing = _listing;
[_vwListingInfoView refresh];
// Modular view: Listing activities
_vwListingActivityView.listing = _listing;
[_vwListingActivityView requestCounts];
}
Every time a new subview is populated, the method viewWillLayoutSubViews is called. This is the method where I compute the subview's height and other constraints and append it to the superview.
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self computeAndFixHeight];
}
- (void) computeAndFixHeight {
// Adjusting each module's height
_cstMainInfoHeight.constant = [_vwListingMainView getViewHeight];
_cstListingActionsHeight.constant = [_vwListingActionsView getViewHeight];
_cstListingAgentsHeight.constant = [_vwListingAgentsView getViewHeight];
_cstListingInfoViewHeight.constant = [_vwListingInfoView getViewHeight];
// Adjusting scroll view height
NSInteger computedScrollHeight = _vwListingUpcomingEventView.frame.origin.y + [_vwListingUpcomingEventView getViewHeight];
[self.scrollView setContentSize:CGSizeMake(self.view.frame.size.width, computedScrollHeight)];
_cstContainerBottom.constant = -computedScrollHeight - kDefaultNegativeScrollH;
[self.view layoutIfNeeded];
[self.view updateConstraints];
}
However, once the view is loaded completely, the problem that I am facing is that sometimes, I get the complete view and sometimes, randomly, I get an empty view. I think [self.view layoutIfNeeded] is the problem, but I have also tried using [self.view setNeedsLayout] and [self.view setNeedsUpdateConstraints], but still the problem remains. Any help would be appreciated.
Please excuse me if I am doing anything stupid. I am new to iOS development.
I created this project just for your question to show how to update a scrollView just using AutoLayout (no need to override viewWillLayoutSubviews, update the scrollView's contentSize, call layoutIfNeeded or updateConstraints)
Hope it helps you =)
https://github.com/ghashi/ScrollViewQuestion/tree/master
This is the result:
In our project we had encountered similar issue where we had to add a lot of views to a content view of a scrollview using constraints added programatically. Writing constraints for each view not only made the view controller bloated, it was also static. To add another view we had to write the constraints again.
We end up creating subclass of UIView that now managed this for us. We named this NNVerticalStackView.h,.m.
I present a simple view with a couple of labels and a button, all inside a UIScrollView and laid out using auto layout.
The button presents another view, which includes a navigation item for dismissal.
After dismissal, though, the content of the original UIScrollView is offset. Strangely, the amount by which it is offset seems related to the scroll position at the time of presentation.
The demo project here is a small example of this issue. Run it in the iPhone simulator and scroll to the bottom to use the 'modal' button. After dismissing the modal attempt to scroll back to the top - the issue should be clear.
Or refer to the scroll bar in the images below to see the problem.
BEFORE PRESENTATION
AFTER PRESENTATION
I'm not an expert in AutoLayout, but I fixed it by adding the label & button constraints to self.view instead of self.scrollView.
For example:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1]"
options:0
metrics:nil
views:#{#"l1":self.l1}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[l1]"
options:0
metrics:nil
views:#{#"l1":self.l1}]];
Why this fixes it... have no idea :D
I've had this same problem, and after much investigation it appears to be a bug in UIKit relating to scrollviews and AutoLayout. Here's the 'fix'...
In viewDidDisappear:, save the current scrollview contentOffset to a property, and reset it to zero:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.previousContentOffset = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointZero;
}
Then, in viewWillAppear:, reset the content offset back to what it was previously. I had to dispatch this onto the main queue to get it to work correctly:
- (void)viewWillAppear:(BOOL)animated
{
if (!CGPointEqualToPoint(self.previousContentOffset, CGPointZero))
{
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollView.contentOffset = self.previousContentOffset;
});
}
}
I'm writing a dynamic wizard application using cocoa/objective c on osx 10.6. The application sequences through a series of views gathering user input along the way. Each view that is displayed is provided by a loadable bundle. When the app starts up, a set of bundles are loaded and as the controller sequences through them, it asks each bundle for its view to display. I use the following to animate the transition between views
[[myContentView animator] replaceSubview:[oldView retain] with:newView];
This works fine most of the time. Every once in a while, a view is displayed and some of the subviews are not displayed. It may be a static text field, a checkbox, or even the entire set of subviews. If, for example, a checkbox is not displayed, I can still click where it should be and it then gets displayed.
I thought it might have something to do with the animation so I tried it like this
[myContentView replaceSubview:[oldView retain] with:newView];
with the same result. Any ideas on what's going on here? Thanks for any assistance.
I don't think is good to use this [oldView retain]. This retain do not make sense. The function replaceSubview will retain it if it is necessary.
Because it work "most of the time" I think it is a memory problem. You try to use a released thing. Test without it and see what happens.
I got the same problem, replaceSubview didn't work.
Finally i found something wrong with my code. so here are the rules :
Both subviews should be in MyContentview's subviews array.
OldView should be the topmost subview on MyContentView or replacesubview will not take place.
here is a simple function to perform replacesubview
- (void) replaceSubView:(NSView *)parentView:(NSView *)oldView:(NSView *)newView {
//Make sure every input pointer is not nill and oldView != newView
if (parentView && oldView && newView && oldView!=newView) {
//if newview is not present in parentview's subview array
//then add it to parentview's subview
if ([[parentView subviews] indexOfObject:newView] == NSNotFound) {
[parentView addSubview:newView];
}
//Note : Sometimes you should make sure that the oldview is the top
//subview in parentview. If not then the view won't change.
//you should change oldview with the top most view on parentview
//replace sub view here
[parentView replaceSubview:oldView with:newView];
}
}
I have an NSTableView subclass with a little animation on it that sometimes changes the width of its columns. It works fine, but if one of the cells is being edited at the time, the field editor stays where it is and does not match the new frame of the cell as it is resized. Whenever I animate the columns, I call the following function in the NSTableView subclass:
- (void) updateFieldEditorPosition {
if([self editedRow]!= -1 && [self editedColumn] != -1) {
NSText *fieldEditor = [[self window] fieldEditor:NO forObject:self];
NSRect editedCellFrame = [self frameOfCellAtColumn:[self editedColumn] row:[self editedRow]];
if(!NSEqualRects([fieldEditor frame], editedCellFrame)) {
[fieldEditor setFrame:editedCellFrame];
[fieldEditor setNeedsDisplay:YES];
[self setNeedsDisplay:YES];
}
}
}
Unfortunately, this doesn't seem to do anything. I have checked that I am, indeed, receiving the correct field editor, and through the debugger I can see that the field editor's frame is being set to the right value and changes over time, but it never actually moves on-screen. What am I missing?
EDIT:
So, I've made an interesting discovery. If, at the same time that I set the NSTableColumn's width, I also set it as hidden and then unhide it again, the field editor moves along with it!
[tableColumn setWidth:newWidth];
[tableColumn setHidden:YES];
[tableColumn setHidden:NO];
I don't know what boolean is getting flipped when I set the NSTableColumn as hidden, but it's working and the field editor is updating itself. It's not the NSTableView's needsDisplay, because if I set that instead of hiding the column nothing happens. Can anyone explain this to me so I can do it a little more elegantly?
I'm working on a custom UITableViewCell subclass, where everything is drawn in code rather than using UILabels etc. (This is part learning exercise and partly because drawing in code is much faster. I know that for a couple of labels it wouldn't make a huge difference, but eventually I'll want to generalise this to more complex cells.)
Currently I'm struggling with the delete button animation: how to animate the cell shrinking as the delete button slides in.
Firstly, I am drawing in a custom subview of the cell's contentView. Everything is drawn in that one subview.
I am setting the subview's size by catching layoutSubviews on the cell itself, and doing:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect b = [self.contentView bounds];
[subcontentView setFrame:b];
}
I'm doing this rather than just setting an autoresizing mask because it seemed more reliable in testing, but I can use the autoresizing mask approach in testing if needed.
Now, the default thing that happens when someone hits the minus is the view gets squished.
I can avoid that by, when setting up my cell, calling
subcontentView.contentMode = UIViewContentModeRedraw;
That gives me the correct end result (my custom view redraws with the new size, and is laid out properly, like the first image I posted), but the animation of the transition is unpleasant: it looks like the cell stretches and shrinks back to size.
I know why the animation is working like that: Core Animation doesn't ask your view to redraw for each frame, it gets it to redraw for the end position of the animation and then interpolates to find the middle bits.
Another solution is to do
subcontentView.contentMode = UIViewContentModeLeft;
That just draws the delete button over my cell, so it covers part of it.
If I also implement
- (void) didTransitionToState:(UITableViewCellStateMask)state
{
[self setNeedsDisplay];
}
then once the delete button has slid in the cell 'jumps' to the correct size. That way there's no nice slidey animation, but at least I get the correct result at the end.
I guess I could run my own animation in parallel with the delete button appearing, temporarily creating another view with a copy of the image of my view in the old size, setting mine to the new size, and fading between them — that way there would be a nice cross fade instead of a sharp jump. Anyone use such a technique?
Now, you might ask why I can't use the contentStretch property and give it a region to resize. The problem with that is I'm making something to be reasonably generic, so it's not always going to be possible. In this particular example it'd work, but a more complex cell may not.
So, my question (after all of this background) is: what do you do in this situation? Does anyone have the animating delete button working for custom drawn cells? If not, what's the best compromise?
This worked for me finally. in subclass of UITableViewCell
subDrawContentView.contentMode = UIViewContentModeLeft;
overide layout subviews method
- (void)layoutSubviews {
CGRect b = [subDrawContentView bounds];
b.size.width = (!self.showingDeleteConfirmation) ? 320 : 300;
[subDrawContentView setFrame:b];
[subDrawContentView setNeedsDisplay];
[super layoutSubviews];
}
So I will paste the code first and then I will explain:
-(void)startCancelAnimation{
[cancelButton setAlpha:0.0f];
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
cancelButton.hidden=NO;
[UIView animateWithDuration:0.4
animations:^(void){
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, 159.0, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, 159.0, text.frame.size.height)];
[cancelButton setFrame:CGRectMake(244., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:1.0f];
} ];
}
-(void)stopCancelAnimation{
[UIView animateWithDuration:0.4
animations:^(void){
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:0.0f];
}completion:^(BOOL completion){
cancelButton.hidden=YES;
[cancelButton setAlpha:1.0f];
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, DEFAULT_WIDTH_PROGRESS, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, DEFAULT_WIDTH_TEXT, text.frame.size.height)];
}
];
}
-(void)decideAnimation{
if([cancelButton isHidden]){
[self startCancelAnimation];
}
else{
[self stopCancelAnimation];
}
}
So what I have there is a button that looks like this:
I have an IBOutlet to it. And what I am doing is resizing a UIProgressView and a UITextField (you can resize whatever you want). As for the code is pretty simple, but if you need any help to understand what's going on, please ask. Also, don't forget to add the Swip Gesture to the UITableView... Like this:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
gesture.numberOfTouchesRequired=1;
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[table addGestureRecognizer:gesture];
[gesture release];
Finally the method that does it all:
-(void)didSwipe:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
//Get the cell of the swipe...
CGPoint swipeLocation = [gestureRecognizer locationInView:self.table];
NSIndexPath *swipedIndexPath = [self.table indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.table cellForRowAtIndexPath:swipedIndexPath];
//Make sure its a cell with content and not an empty one.
if([swipedCell isKindOfClass:[AMUploadsTableViewCell class]]){
// It will start the animation here
[(AMUploadsTableViewCell*)swipedCell decideAnimation];
// Do what you want :)
}
}
}
So as you can see the whole animation is created manually, so you can control exactly what you want. :)