My app has two different states, and every state will be representing with a NSView.
so there is only one view showed at a time, the problem is when I switched between the views the app doesn't show the new state until I resize the window manually!
I searched about this problem and come with more than one solution but nothing worked with me:
[myView setNeedsDisplay:YES];
[myView display];
[[myView.window contentView] setNeedsDisplay:YES];
[mySubView1 setHidden:YES]; || [mySubView1 removeFromSuperView];
I even defined myView as Outlet, and nothing work.
and this is my code
if (appState == 1) {
[self.splitView setFrameSize:CGSizeMake(self.splitView.frame.size.width, self.view.frame.size.height - 250)];
[self.mySubView1 setHidden:NO];
[self.mySubView2 setHidden:YES];
[self.mySubView2 removeFromSuperview];
[self.mySubView1 addSubview:self.inCallView];
}
else
{
[self.splitView setFrameSize:CGSizeMake(self.splitView.frame.size.width, self.view.frame.size.height - 70)];
[self.mySubView1 setHidden:YES];
[self.mySubView2 setHidden:NO];
[self.mySubView1 removeFromSuperview];
[self.mySubView2 addSubview:self.chatHeaderView];
}
// I need to redraw here
[self.view setNeedsDisplay:YES];
[self.mySubView1 setNeedsDisplay:YES];
[self.mySubView2 setNeedsDisplay:YES];
// and nothing happened until I resize my window manually
I found it, the code is just fine and no need to call any redraw method, the only problem Any UI action need to be done in the MAIN thread
so the final code will be:
dispatch_async( dispatch_get_main_queue(), ^{
if (appState == 1) {
[self.splitView setFrameSize:CGSizeMake(self.splitView.frame.size.width, self.view.frame.size.height - 250)];
[self.mySubView1 setHidden:NO];
[self.mySubView2 setHidden:YES];
}
else
{
[self.splitView setFrameSize:CGSizeMake(self.splitView.frame.size.width, self.view.frame.size.height - 70)];
[self.mySubView1 setHidden:YES];
[self.mySubView2 setHidden:NO];
}
});
Thanks, guys.
doing a subview remove or add operation will automatically call setNeedsDisplay:YES so it's no surprise that calling it manually is having no effect.
some things to check:
Check your view properties for nil values. I see you are removing the view which will cause the superview to release it and if you aren't retaining it with a strong property (or elsewhere) it will be deallocated.
Be sure that you aren't fighting autolayout. turn off autolayout temporarily to confirm it is not causing your problem.
you are only setting the frame size. you need to make sure it has the expected origin as well.
Also if you are removing the view you don't need to bother calling setHidden:
Related
I have implemented refreshControl as shown below in viewDidLoad():
refreshControl = [[UIRefreshControl alloc] init];
if (#available(iOS 10.0, *)) {
self.tableView.refreshControl = refreshControl;
} else {
[self.tableView addSubview:refreshControl];
}
Now I am presenting another viewController which has options to select filters. And after selecting those filters you come back again to current viewController having refreshControl.
I have added below code in viewDidAppear() for manually calling beginRefreshing:
if (self.filterChanged) {
self.filterChanged = NO;
[self.activityTableView setContentOffset:CGPointMake(0, - refreshControl.frame.size.height) animated:YES];
[refreshControl setHidden:NO];
[refreshControl beginRefreshing];
}
I have used setContentOffset for scrolling back to top and showing refreshControl.
The only problem is suppose my tableView is half scrolled in between then there is a big gap between refreshControl.
If my tableView is not scrolled then it works fine like I have pulled down to refresh, but if it is half scrolled then inspite of giving setContentOffset there is a big gap between refreshControl and tableview.
You need to wait for the scroll to finish before you can start the refresh. Unfortunately there is no completion block on setContentOffset, so try this.
After creating your refresh control set the target and create a corresponding method.
[_refreshControl addTarget:self action:#selector(refreshRequested) forControlEvents:UIControlEventValueChanged];
- (void)refreshRequested {
[_activityTableView reloadData];
}
In your viewDidAppear you scroll to top and refresh when done.
[UIView animateWithDuration:0.25 delay:0 options:0 animations:^(void){
self.activityTableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
} completion:^(BOOL finished){
[self.refreshControl beginRefreshing];
[self refreshRequested];
}];
When finished loading you need to end refresh and make sure to scroll back to zero position.
- (void)finishedLoading {
[_refreshControl endRefreshing];
[self.activityTableView setContentOffset:CGPointMake(0, 0) animated:YES];
}
Migrating to AVkit from MPMoviePlayer has brought me to a blocking issue.
I need to display a custom tableView over the AVPlayerViewController.
I can this tableview trough
[self.avVideoPlayer.contentOverlayView addsubiew:self.mycustomTableView]
and it is visible but it doesn't receive any tap/swipe events.
Any ideas why this happens or how can I add the table view in a place where it would receive the touch events even in the fullscreen mode?
[self.view addSubview:btn];
This will add the button in the minimised player.
Now for the fullscreen scenario you need to add an observer for the videoBounds, here is my code:
[self.avVideoPlayer addObserver:self forKeyPath:#"videoBounds" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
Now here comes the not so pretty part:
- (void)observeValueForKeyPath: (NSString*) path
ofObject: (id)object
change: (NSDictionary*)change
context: (void*)context {
NSLog(#"SOME OBSERVER DID THIS: %# , %#",object,change);
if ([self playerIsFullscreen]) {
for(UIWindow* tempWindow in [[UIApplication sharedApplication]windows]){
for(UIView* tempView in [tempWindow subviews]){
if ([[tempView description] rangeOfString:#"UIInputSetContainerView"].location != NSNotFound){
UIButton *testB = [[UIButton alloc] initWithFrame: CGRectMake(20, 20, 400, 400)];
testB.backgroundColor = [UIColor redColor];
[testB addTarget:self action:#selector(buttonTouch) forControlEvents:UIControlEventTouchDown];
[tempView addSubview:testB];
break;
}
}
}
}
}
I know this is a not a nice way to do it, but it is the only way I managed to add some custom UI that also receives touch events on the player.
Cheers!
[_articleTxtView setFont:[UIFont systemFontOfSize:_fontSizeInt]];
[_articleTxtView layoutIfNeeded];
[_articleTxtView sizeToFit];
The above code is triggered with a press of a UIButton and responsible of changing the size of a UITextView. Every time the UIButton is pressed the _fontSizeInt changes to a bigger number and the UITextView height changes accordingly.
The problem is that layoutIfNeeded and sizeToFitare called before setFont is finished and cuts UITextView in the middle.
Possible solutions:
This solution works great but I'd preffer not using something with such a bad practice.
..
[_articleTxtView setFont:[UIFont systemFontOfSize:[DataManager sharedDataManager].fontSize]];
[self performSelector:#selector(test) withObject:nil afterDelay:0.01];
}
- (void)test
{
[_articleTxtView layoutIfNeeded];
[_articleTxtView sizeToFit];
}
I thought about using the next code but for some reason it doesn't always work. Also I'm not sure if that's a proper use of the animation block (there's really no animation involved):
[UIView animateWithDuration:0.4 animations:^()
{
[_articleTxtView setFont:[UIFont systemFontOfSize:[DataManager sharedDataManager].fontSize]];
}
completion:^(BOOL finished)
{
[_articleTxtView layoutIfNeeded];
[_articleTxtView sizeToFit];
}];
Is there a better way to let a method "know" when a UIView finished painting \ loading?
Thanks
You could implement viewDidLoad (called once) or viewWillAppear (called every time) in the articleTxtView view controller class
Background & Goal
I have a window and three sepearate views in a nib file in IB. The three views are used for the three 'parts' of the program: the first were the user drops a file, the second where it chooses some options and the third where it is presented with a progress bar as the program accomplishes a task.
I want to animate between those views as they change, and I'm doing a fade-in/out animation. The goal is to have the old view (the one that is going to disappear) fade out as the new view fades in. The views have different sizes, so I animate the window frame as well at the same time (those views fill up the window). All three views have Core Animation enabled in IB, but nothing else (the window's content view for example) has.
Code
At the start of the program, I register the views' sizes so I can use them later, add the views to the window and set the 2nd and 3rd view hidden.
- (void)awakeFromNib
{
// Register native views' size (when they get displayed again the window can be set to their bounds)
view1NativeSize = [firstView bounds].size;
view2NativeSize = [secondView bounds].size;
view3NativeSize = [thirdView bounds].size;
// Add views to the main window's content view
NSRect viewFrame = [[mainWindow contentView] bounds];
[firstView setFrame: viewFrame];
[secondView setFrame: viewFrame];
[thirdView setFrame: viewFrame];
[[mainWindow contentView] addSubview: firstView];
[[mainWindow contentView] addSubview: secondView];
[[mainWindow contentView] addSubview: thirdView];
// Set their attributes
[secondView setHidden: TRUE];
[thirdView setHidden: TRUE];
currView = 1;
}
Then I have three methods for swapping between views, where I calculate the new window frame and call a method for the animation.
- (IBAction)goToFirstView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view1NativeSize;
newFrame.size.height += [self titleBarHeight]; // Method that returns the title bar height
if (currView == 1) {
return;
}
else if (currView == 2) {
[self animateFromView: secondView toView: firstView andWindowFrame: newFrame];
currView--;
}
else if (currView == 3) {
[self animateFromView: thirdView toView: firstView andWindowFrame: newFrame];
currView -= 2;
}
}
- (IBAction)goToSecondView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view2NativeSize;
newFrame.size.height += [self titleBarHeight];
if (currView == 2) {
return;
}
else if (currView == 1) {
[self animateFromView: firstView toView: secondView andWindowFrame: newFrame];
currView++;
}
else if (currView == 3) {
[self animateFromView: thirdView toView: secondView andWindowFrame: newFrame];
currView--;
}
}
- (IBAction)goToThirdView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view3NativeSize;
newFrame.size.height += [self titleBarHeight];
if (currView == 3) {
return;
}
else if (currView == 1) {
[self animateFromView: firstView toView: thirdView andWindowFrame: newFrame];
currView += 2;
}
else if (currView == 2) {
[self animateFromView: secondView toView: thirdView andWindowFrame: newFrame];
currView++;
}
}
The method for the animation is pretty straightforward. I animate the views.
- (void)animateFromView: (NSView*)oldView
toView: (NSView*)newView
andWindowFrame: (NSRect)newWindowFrame
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: 0.5];
[[newView animator] setHidden: FALSE];
[[oldView animator] setHidden: TRUE];
//[[mainWindow animator] setFrame: newWindowFrame]; --Doesn't work??
[NSAnimationContext endGrouping];
[mainWindow setFrame: newWindowFrame display: YES animate: YES];
}
Questions
I have two problems with this (most important first):
On the third view there is a progress indicator and it never displays correctly. It doesn't update! I call -startAnimation: but it doesn't move at all. If i turn off CA on that third layer it won't fade in/out correctly but the progress bar starts to work (animate). No clue why...
In my numerous experiments trying to solve the first problem I tried turning on CA for the window's content view and turning off CA for the three subviews. The first problem wasn't solved, but I noticed something: that way the old view was fading out AS the new view faded in, while before that change (subviews with CA) I could only notice the new view fading in. I recorded it and watched it in sloooowww-moootion :) and found I was right: the old view magically disappears and all I see is the new view fading in. It's funny I only found that after watching the animation I really wanted. Am I supposed to turn CA on only for the content view or for all three subviews separately? (or is this problem from something else?)
I tried many things like turning on CA for the progress bar. I found also many questions here that are close to this one but don't specifically address the progress bar problem.
Good luck finding a solution :)
To handle your crossfade you could just use a CATransition as explained in this answer but for a crossfade you wouldn't need to set the transition type as it is the default.
I'm afraid I don't have an answer for the progress bar problem though, it may be something to do with the unorthodox way that you're doing the crossfade.
I need to replicate the function of bringSubviewToFront: on the iPhone, but I am programming on the Mac. How can this be done?
Haven't actually tried this out - and there may be better ways to do it - but this should work:
NSView* superview = [view superview];
[view removeFromSuperview];
[superview addSubview:view];
This will move 'view' to the front of its siblings
Pete Rossi's answer didn't work for me because I needed to pop the the view to front when dragging it with the mouse. However, along the same lines the following did work without killing the mouse:
CALayer* superlayer = [[view layer] superlayer];
[[view layer] removeFromSuperlayer];
[superlayer addSublayer:[view layer]];
Also, the following placed in a NSView subclass or category is pretty handy:
- (void) bringToFront {
CALayer* superlayer = [[self layer] superlayer];
[[self layer] removeFromSuperlayer];
[superlayer addSublayer:[self layer]];
}
also be sure to enable the layer for quartz rendering:
...
NSImageView *anImage = [[NSImageView alloc] initWithFrame:NSRectMake(0,0,512,512)];
[anImageView setWantsLayer:YES];
...
otherwise, your layer cannot be rendered correctly.
Pete Rossi's answer works, but remember to retain the view when you remove it from the superview.
You can add this in a category on NSView :
-(void)bringSubviewToFront:(NSView*)view
{
[view retain];
[view removeFromSuperview];
[self addSubview:view];
[view release];
}
Sibling views that overlap can be hard to make work right in AppKitāit was completely unsupported for a long time. Consider making them CALayers instead. As a bonus, you may be able to reuse this code in your iOS version.
This is Swift 3.0 solution:
extension NSView {
public func bringToFront() {
let superlayer = self.layer?.superlayer
self.layer?.removeFromSuperlayer()
superlayer?.addSublayer(self.layer!)
}
}