I have 2 monitors, and I'd like to get my videoWindow to be placed and scaled to the size of the second monitor. I'd like to do this programmatically, as the second monitor resolution may change. I'm able to get the window placed in the bottom-left of the second monitor, but I'm not able to scale it to fit.
The warning on this line:
[self.videoWindow setFrame: screenRect];
Is: 'NSWindow' may not respond to 'setFrame'
// inside my .h file
#property (assign) IBOutlet NSWindow *videoWindow;
// inside my .m file
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
NSRect videoPreviewScreenRect;
NSArray *screenArray = [NSScreen screens];
//Using index of 1, to get secondary monitor
NSScreen *videoPreviewScreen = [screenArray objectAtIndex: 1];
NSRect screenRect = [videoPreviewScreen frame];
videoPreviewScreenRect = [videoPreviewScreen visibleFrame];
// Get and set the screen origin based on the second monitors origin
NSPoint videoScreenOrigin ;
videoScreenOrigin.x = videoPreviewScreenRect.origin.x;
videoScreenOrigin.y = videoPreviewScreenRect.origin.y;
[self.videoWindow setFrameOrigin: videoScreenOrigin];
// **** THIS LINE DOESN'T WORK ****
[self.videoWindow setFrame: screenRect];
[self.videoWindow setBackgroundColor: NSColor.blackColor];
[self.videoWindow display];
[self.videoWindow makeKeyAndOrderFront:nil];
}
I was able to figure out what the issue was.
[self.videoWindow setFrame: screenRect];
needed to be changed to this:
[[self videoWindow] setFrame:screenRect display:YES animate:NO];
Related
I'm initializing a loader subview to match the height, width and position of the UITabBar I'm using to wrap my app:
// In UITabBarController implementation
LoaderView *loaderView = [[LoaderView alloc] initWithFrame:[self tabBar].viewForBaselineLayout.frame];
[[self view] addSubview:loaderView];
//
// LoaderView.h
#import <UIKit/UIKit.h>
#interface LoaderView : UIView
#property (nonatomic, strong) UILabel *messageLabel;
#property (nonatomic, strong) NSString *message;
#property (nonatomic) CGRect frame;
- (void)createLabel;
- (void)drawLoader;
- (void)setText:(NSString *)newMessage;
- (void)show:(NSNotification *)notification;
#end
//
// LoaderView.m
#import "LoaderView.h"
#implementation LoaderView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self drawLoader];
}
return self;
}
- (void)drawLoader
{
UIColor *semiOpaqueGray = [[UIColor alloc] initWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f];
[self setBackgroundColor:semiOpaqueGray];
[self createLabel];
}
- (void)createLabel
{
_messageLabel = [[UILabel alloc] initWithFrame: CGRectMake(15,9,([self frame].size.width - 10), 30)];
_messageLabel.textColor = [[UIColor alloc] initWithWhite:1.0 alpha:1];
_messageLabel.backgroundColor = [[UIColor alloc] initWithWhite:1.0 alpha:0.0];
[self addSubview:_messageLabel];
}
#end
The frame struct represents this incoming frame data:
2013-09-16 07:48:35.552 ---[97825:a0b] {{0, 519}, {320, 49}}
// Ostensibly 0,519 origin point and 320,49 w/h
The result is this. The mostly opaque dark box can be spotted in the top left corner. It looks like it's being positioned by its center point of the loader to the top left most point of the screen:
I can make the size of the box change, but I can't seem to move it from that top left position. Further, I set an animation on it, and that animation adjusts the frame (sliding it up an down from the tab bar area). That seems to have no effect either.
Thanks in advance for your help.
You can gently add your loadingView your application window. After set the frame of loaderView.
[loadView setFrame:CGRectMake(0,0,self.frame.size.width.,self.frame.size.height)];
[self.window addSubview:loaderView];
Then after loading finished.
[self.window removeSubview:loaderView];
Your superview is the problem. If you're adding it to your UINavigationController then the y-axis likely won't respond nicely. Try using the primary view of your controller
loaderView.frame = CGRectMake(x, 514, 40, 320); // made up numbers
[self.mapView addSubview:loaderView];
Also you're accessing your tabBar. Try using UIDevice window for getting your width and inherit the height from your labels on the bottom (or nest the labels within your LoaderView)
Also look into actually using UITabBar for handling this.
I having problems getting an accurate reading for heights and widths. So I made this quick and dirty app to test the situation.
Here is the code:
#interface wwfpViewController ()
#property (nonatomic, strong) UIView * box;
#property (nonatomic, strong) UILabel * info;
#end
#implementation wwfpViewController
#synthesize box,info;
- (void)viewDidLoad {
[super viewDidLoad];
box=[[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[box setBackgroundColor:[UIColor darkGrayColor]];
[box setAutoresizesSubviews:YES];
[box setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
info=[[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 300)];
[info setTextColor:[UIColor whiteColor]];
[info setBackgroundColor:[UIColor blackColor]];
[info setLineBreakMode:NSLineBreakByWordWrapping];
[info setNumberOfLines:10];
[info setText:#"..."];
[self.view addSubview:box];
[box addSubview:info];
[self updateInfo];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateView:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
}
- (void) updateInfo {
CGFloat selfHeight=self.view.frame.size.height;
CGFloat selfWidth=self.view.frame.size.width;
CGFloat boxHeight=box.frame.size.height;
CGFloat boxWidth=box.frame.size.width;
int deviceOrientation=[[UIDevice currentDevice] orientation];
int statusOrientation=[[UIApplication sharedApplication] statusBarOrientation];
NSString * str=[NSString stringWithFormat:#"[height x width] \nself: [%f x %f] \nbox: [%f x %f] \ndevice: %d status: %d",selfHeight,selfWidth,boxHeight,boxWidth,deviceOrientation,statusOrientation];
[info setText:str];
}
- (void) updateView: (NSNotification*) notify {
[self updateInfo];
}
#end
When I test this on an iPad, initially in portrait mode, the info label reports the following:
[height x width]
self: [1004.000000 x 768.000000]
box: [1004.000000 x 768.000000]
device: 0 status: 1
This is correct!
And then when I rotate the iPad to landscape, I get these readings:
[height x width]
self: [768.000000 x 1004.000000]
box: [1004.000000 x 768.000000]
device: 3 status: 3
Actual height x width: 748 x 1024
But when I test this on the iPad when its in landscape orientation, the info label reports:
[height x width]
self: [1024.000000 x 748.000000]
box: [1024.000000 x 748.000000]
device: 0 status: 3
Actual height x width: 748 x 1024
Then when I rotate the iPad to portrait, I get these readings:
[height x width]
self: [748.000000 x 1024.000000]
box: [748.000000 x 1024.000000]
device: 1 status: 1
Actual height x width: 1004 x 768
I rotate it back to landscape and then I get these readings:
[height x width]
self: [768.000000 x 1004.000000]
box: [1004.000000 x 768.000000]
device: 3 status: 3
Actual height x width: 748 x 1024
In all cases the box UIView covers the entire screen, so it is auto adjusting to the orientation changes correctly. These results are consistent from the simulator and testing it on an actual iPad, and I have similar experiences on an iPhone.
After this, I have a few questions:
What am I doing wrong?
Why is the height and width for self.view different from the height and width for box when the two look visually identical?
How can I accurately obtain the overall height and width of the screen or a view, irrespective of orientation changes.
Because [UIDevice ... orientation] reports as zero the first time it is used, should I just ignore it altogether and just stick with [UIApplication ... statusBarOrientation]?
Check the bounds of the views rather than the frame:
CGFloat selfHeight=self.view.bounds.size.height;
CGFloat selfWidth=self.view.bounds.size.width;
CGFloat boxHeight=box.bounds.size.height;
CGFloat boxWidth=box.bounds.size.width;
Also, I would use the UIViewController method for orientation changes and remove the NSNotificationCenter observer.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[self updateInfo];
}
Finally, the first call to get the correct size should be in viewWillAppear as it will be incorrect in viewDidLoad:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self updateInfo];
}
I tried your code and found one mistake here.
Cause of bug- You didn't make app navigationController based in appdelegate. Set your window's rootViewController to navigationController don't know why but viewController's based view doesn't give the correct frame.
My Opinion- Don't use notification here when you have already a method -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation which is call after orientation changed. Well You are using notification then NP this is just my opinion.
One more thing I would like to say
box = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
Why don't you simply do
box = [[UIView alloc] initWithFrame:self.view.bounds];
Your Code is okay. Just set your window's rootViewController to navigationController OR tabbarController.
This is Sample Code.
From Apple docs
Discussion When a view’s bounds change, that view automatically
resizes its subviews according to each subview’s autoresizing mask.
You specify the value of this mask by combining the constants
described in UIViewAutoresizing using the C bitwise OR operator.
Combining these constants lets you specify which dimensions of the
view should grow or shrink relative to the superview. The default
value of this property is UIViewAutoresizingNone, which indicates that
the view should not be resized at all.
When more than one option along the same axis is set, the default
behavior is to distribute the size difference proportionally among the
flexible portions. The larger the flexible portion, relative to the
other flexible portions, the more it is likely to grow. For example,
suppose this property includes the UIViewAutoresizingFlexibleWidth and
UIViewAutoresizingFlexibleRightMargin constants but does not include
the UIViewAutoresizingFlexibleLeftMargin constant, thus indicating
that the width of the view’s left margin is fixed but that the view’s
width and right margin may change. Thus, the view appears anchored to
the left side of its superview while both the view width and the gap
to the right of the view increase.
If the autoresizing behaviors do not offer the precise layout that you
need for your views, you can use a custom container view and override
its layoutSubviews method to position your subviews more precisely.
It probably has to do with autoresizing masks read more about it here.
myview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
Working for me
I've made macros vWidth and vHeight that will handle all of this logic properly, with fallbacks:
#define sWidth [[UIScreen mainScreen] bounds].size.width
#define sHeight [[UIScreen mainScreen] bounds].size.height
#define vWidth (\
(^float (void){\
BOOL isClassMethod = [[self class] respondsToSelector:_cmd];\
if (isClassMethod) {\
return sWidth;\
} else {\
float __vWidth = ([self isKindOfClass:[UIViewController class]]?((UIViewController *)self).view.frame.size.width:((UIView *)self).frame.size.width);\
if (!__vWidth) {\
__vWidth = ([self isKindOfClass:[UIViewController class]]?((UIViewController *)self).view.superview.frame.size.width:((UIView *)self).superview.frame.size.width);\
}\
if (!__vWidth) {\
__vWidth = sWidth;\
}\
return __vWidth;\
}\
})()\
)
#define vHeight (\
(^float (void){\
BOOL isClassMethod = [[self class] respondsToSelector:_cmd];\
if (isClassMethod) {\
return sHeight;\
} else {\
float __vHeight = ([self isKindOfClass:[UIViewController class]]?((UIViewController *)self).view.frame.size.height:((UIView *)self).frame.size.height);\
if (!__vHeight) {\
__vHeight = ([self isKindOfClass:[UIViewController class]]?((UIViewController *)self).view.superview.frame.size.height:((UIView *)self).superview.frame.size.height);\
}\
if (!__vHeight) {\
__vHeight = sHeight;\
}\
return __vHeight;\
}\
})()\
)
i am new to programming, objective-c (and stackoverflow). I'm learning and moving forward very slowly ;) but then I ran into a problem, which google couldn't solve. I have a single window and a NSview and then added a mouse event that should draw the coordinates into my view, but it doesn't. The funny thing is: it is drawn when the mouse moves over the window buttons of my apps window...
- (void)drawRect:(NSRect)dirtyRect {
NSPoint imagePos = NSMakePoint(0, 0);
NSImage *aImage = [NSImage imageNamed:#"mw_bg01.png"];
[aImage dissolveToPoint:imagePos fraction:1.0];
}
- (void)mouseDown:(NSEvent*)theEvent;{
mouseLoc = [NSEvent mouseLocation];
mousePosX = mouseLoc.x;mousePosY = mouseLoc.y;
NSString* mouseString = [NSString stringWithFormat:#"%d", mousePosX];
NSPoint textPoint = NSMakePoint(5, 5);
NSMutableDictionary *textAttrib = [[NSMutableDictionary alloc] init];
[textAttrib setObject:[NSFont fontWithName:#"Helvetica Light" size:10]
forKey:NSFontAttributeName];
[textAttrib setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
[mouseString drawAtPoint:textPoint withAttributes:textAttrib];
}
I don't know how to go on, any suggestions? Thanks!
You shouldn't do drawing in the -mouseDown: method. Rather, you must do all your drawing in -drawRect: (or methods that you call from -drawRect:). Try something like this:
#interface MyView ()
#property NSPoint lastMousePoint;
#end
#implementation MyView
- (void)drawLastMousePoint
{
NSString *mouseString = NSStringFromPoint(self.lastMousePoint);
NSPoint textPoint = NSMakePoint(5, 5);
NSMutableDictionary *textAttrib = [[NSMutableDictionary alloc] init];
[textAttrib setObject:[NSFont fontWithName:#"Helvetica Light" size:10]
forKey:NSFontAttributeName];
[textAttrib setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
[mouseString drawAtPoint:textPoint withAttributes:textAttrib];
}
- (void)drawRect:(NSRect)dirtyRect
{
NSPoint imagePos = NSMakePoint(0, 0);
NSImage *aImage = [NSImage imageNamed:#"mw_bg01.png"];
[aImage dissolveToPoint:imagePos fraction:1.0];
[self drawLastMousePoint];
}
- (void)mouseDown:(NSEvent*)theEvent;
{
self.lastMousePoint = [theEvent locationInWindow];
[self setNeedsDisplay:YES];
}
#end
When you get a mouse down event, you simply store the location of the mouse down. The drawing is done in -drawLastMousePoint which you call in your -drawRect: method. Since you know you need to redraw anytime the mouse is clicked, you call -setNeedsDisplay: to inform the view that it needs to be redrawn. Note that the redraw doesn't happen immediately, rather, it will happen the next time through the run loop. In other words, you're saying "hey, something changed, and I need to draw my view's contents again. Please call -drawRect: again as soon as possible!"
One other note: +[NSEvent mouseLocation] is really designed for getting the current mouse location outside the event stream. Typically, in a -mouseDown: method, you call -locationInWindow on the NSEvent passed as the argument to the method. If you need to convert to local/view coordinates, you should call [self convertPoint:[theEvent locationInWindow] fromView:nil];.
I am trying to add a (fake)3d like effect for an image (UIImageView moving from point A to B, during this movement I want at point C=(A+B)/2 for it to have the biggest shadow size (or larger shadow offset), so it looks like it is going up and down again.
when I try to even change the shadow size, it is not animating. could you help me how to edit this code:
NSValue *pointB = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMinX(imageView.frame)+50, CGRectGetMinY(imageView.frame)+50)];
[self.view bringSubviewToFront:ImageView];
[UIView beginAnimations:#"UIImage Move" context:NULL];
CGPoint point = [pointB CGPointValue];
CGSize size =imageView.frame.size;
[UIView setAnimationDuration:1.0];
imageView.frame = CGRectMake(point.x, point.y, size.width, size.height);
imageView.layer.shadowOffset = CGSizeMake(0, 4); //actually I want this to happen in mid point and revert to offset 1
[UIView commitAnimations];
//sorry for possible problems with syntax, the code works fine, I had to rewrite and simplify it for understanding
You need to animate the shadowOffset of the layer by using CAAnimation. Here is an example on how to enlarge the shadowOffset while moving the object. This example uses a UIButton.
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface ViewController : UIViewController
#property (nonatomic, retain) IBOutlet UIButton *button;
#end
In the M file I am calling the animations on the button from the buttons IBAction.
-(IBAction)shadowGrow:(id)sender {
CABasicAnimation *shadowGrow = [CABasicAnimation animationWithKeyPath:#"shadowRadius" ];
shadowGrow.delegate = self;
[shadowGrow setFromValue:[NSNumber numberWithFloat:3.0]];
[shadowGrow setToValue:[NSNumber numberWithFloat:20.0]];
[shadowGrow setDuration:1.0f];
shadowGrow.autoreverses = YES;
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"transform.translation.x" ];
move.delegate = self;
[move setFromValue:[NSNumber numberWithFloat:0]];
[move setToValue:[NSNumber numberWithFloat:50]];
[move setDuration:1.0f];
move.autoreverses = YES;
//Add animation to a specific element's layer. Must be called after the element is displayed.
[[button layer] addAnimation:shadowGrow forKey:#"shadowRadius"];
[[button layer] addAnimation:move forKey:#"transform.translation.x"];
}
One thing to remember with CoreAnimation is when animating the properties like this they are going to revert to their value from the start unless you set those values after the animation ends in the CAAnimation's Delegate Method.
- (void) animationDidStop:(NSString *)theAnimation finished:(NSNumber *)finished context:(void *)context
Here is some additional information on CALayer's animatable properties.
CALayer and CIFilter animatable properties
I am trying to make an overlay window that will allow drawing at the ShieldingWindowLevel, however when the window appears the cursor is still the default pointer. I would like to change it to the crosshairs. Having controller NSCursors before I am baffled why resetCursorRects is not ever called.
I manually create the window as follows (in my AppController class):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Create the window
NSRect frame = [[NSScreen mainScreen] frame];
// Provide a small area on the right to move the cursor in-and-out of the window.
frame.size.width = frame.size.width - 20;
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.window setAcceptsMouseMovedEvents:YES];
[self.window setOpaque:NO];
[self.window setLevel:CGShieldingWindowLevel()];
[self.window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.2]];
// Create the subview
ScreenOverlayView *subview = [[ScreenOverlayView alloc] initWithFrame:NSZeroRect];
[[self.window contentView] addSubview:subview];
// Add subview and show window
[self.window setContentView:subview];
[self.window makeFirstResponder:subview];
[self.window orderFrontRegardless];
}
With the following NSView subclass:
#implementation ScreenOverlayView
- (void) resetCursorRects {
[super resetCursorRects];
[self addCursorRect: [self bounds]
cursor: [NSCursor crosshairCursor]];
}
// ...
#end
I created a sample project to show this case and posted it to github, the most interesting files are ScreenOverlayView.m and AppDelegate.m.
I should point out that I have also spent a good deal of time trying to get this working with an NSTrackingArea, as you can see in the sample project. Tracking Area works if the mouse enters the view after it has appeared, but not if it is inside to start with. Using MouseEnter and MouseLeave would be fine if I had some way to set the initial cursor, but it will only change for a split second before changing back.
How can I get resetCursorRects to be invoked -OR- how can I set the cursor when I move it to the superview?
The key is that you really need to create a custom subclass of NSWindow, in order to counteract some of the default behavior that borderless windows (NSBorderlessWindowMask) have.
An updated version of your sample project is at http://www.markdouma.com/developer/full-screen-overlay.zip.
In it, I created a custom MDScreenOverlayWindow class that overrides NSWindow's canBecomeKeyWindow method like below:
// Windows created with NSBorderlessWindowMask normally can't be key,
but we want ours to be
- (BOOL)canBecomeKeyWindow {
return YES;
}
This will allow your view to become key and basically all your other stuff to work properly.
The other thing that may be of note is the drawRect: method. (It looks like you may be coming from iOS). You might want to look into NSBezierPath, as it could potentially simplify some of your drawing code. For example, I believe the drawing code you had could be consolidated into the following:
- (void)drawRect:(NSRect)rect {
// the color should probably be "pre-multiplied" by the alpha
// premultiplied version:
[[NSColor colorWithCalibratedRed:0.8 green:0.0 blue:0.0 alpha:0.8] set];
[NSBezierPath setDefaultLineWidth:2.0];
[NSBezierPath strokeLineFromPoint:currentLocation toPoint:downLocation];
}