Trouble Setting Frames of NSView - objective-c

I have no idea why this code isn't working, but hopefully the fix is simple, I hope someone can help.
I have 2 functions, enterFullScreen and exitFullScreen.
In the enterFullScreen method, I set an NSView's frame to the Window's frame, which has a full-screen effect, this is what I want. Within this method I store the old frames so that in the exitFullScreen method I can set them back, but this isn't working. It has no effect whatsoever. The code is as follows:
NSView oldView, oldViewContainer;
- (void) enterFullScreen:(NSView*)newView
{
// Store original views
oldView = newView;
oldViewContainer = _newViewContainer;
// Set new views to fit window
[_newViewContainer setFrame:[_window.contentView frame]];
[newView setFrame:[_window.contentView frame]];
}
- (void) exitFullScreen:(NSView*)newView
{
// Restore old views
[_newViewContainer setFrame:[oldViewContainer frame]];
[newView setFrame:[oldView frame]];
}
The enter fullscreen method works exactly as expected, but for some reason I can't set the 2 Views back to their original size/location.
I don't know if it's because I can't just store the whole view, or if the origin is causing problems?
I've tried every combination, if somebody could help I'd be really grateful.
Thanks in advance.

You don't want to set oldView to newView, that makes them the same view, so when you resize newView to full screen, oldView's frame also is set to full screen. You want to save the old frame, not the view, so:
oldFrame = newView.frame; (to store the original frame)
then when you resize smaller,
[newView setFrame:oldFrame];

Related

Why NSView leaves an image on superview on where it was when I move it?

I am working on a small application on Mac that I need to create customed cursor and move it. I used NSImageView to implement it. However when I call setFrameOrigin (the same to setFrame) it will leaves images on the previous place.
Here is my code:
#property (nonatomic, strong) NSImageView *eraserView;
this is the define
_eraserView = [[NSImageView alloc] initWithFrame:CGRectMake(100, 100, 32, 32)];
_eraserView.image = [NSImage imageNamed:#"EraserCursor"];
[self.view addSubview:_eraserView];
[_eraserView setHidden:YES];
here is the initialization. Everything goes well until now but:
- (void)setImageatPoint:(NSPoint)point
{
[_eraserView setFrameOrigin:point];
}
- (void)hidePenImage
{
[_eraserView setHidden:YES];
}
- (void)unhidePenImage: (BOOL)isEraser
{
[_eraserView setHidden:NO];
}
These are methods I use to change the state of the NSImageView. They will be called by another class using delegate when corresponding events of trackpad occurs.
However every time I change the state of the NSImageView, it seems like it is drawn on the superview.
I debugged it and found there was no extra subviews. And when I use setHidden it has no effect on those tracks. I think it somehow did something to the CALayer, but I have no idea how to fix it.
Screenshots would help but in general if you move a view or change the area of the view that is drawn, you need to redraw.
To do this it kind of depends on how your drawing happens. Calling setNeedsDisplay may not be enough if your implementation of drawRect only draws a sub rect of the view bounds. Cocoa only draws what it is told to draw.
You can erase sections of the view that should be empty by drawing (filling) where it should be empty. That means drawing a color ( NSColor clearColor if nothing else) in the area that was previously drawn.

UIScrollView - Custom Map - Prevent marker subview on map from scaling with map

I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.
I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.
I haven't been able to find any information on this, what would be the best way to go about this?
If there is anything I can provide you with to help the answer, please let me know.
Many thanks!
EDIT -
Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.
EDIT 2-
Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.
I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).
Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:
CGPoint ULPC = userLocationPlaceholder.center;
float zs = scrollView.zoomScale;
CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);
Where the image is 40points wide. This matches the centers perfectly.
The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.
I have tried using a self calling animation as such:
-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
animations:^{
userLocationDotContainer.frame = newFrame;
} completion:^(BOOL finished){
// Call self
[self animateUserLocationAttachment];
}];
}
As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.
Is there any way I can get around this, or an alternative method I can apply?
Many thanks
EDIT 3 -
I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.
The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.
The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.
I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).
I did subclass UIImageView and implement setTransform: method like here:
#import "SLImageView.h"
#implementation SLImageView
- (void)setTransform:(CGAffineTransform)transform
{
[super setTransform:transform];
CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);
for (id obj in self.subviews)
{
if ([obj isKindOfClass:[MKAnnotationView class]])
{
[((UIView *)obj) setTransform:invertedTransform];
}
}
}
#end
This works perfectly!
Mick.
Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.
Relative coordinate systems
Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.
Get the origin of the container view
Get the location of the subview inside of the container view
Get the origin of the subview
Calculate the difference in the positions of the origins
Get the location of the object you want to overlap (relative to the subview)
Calculate the location of the object you want to overlap relative to the container view
Move your crosshair to this position
Swift equivalent for Mick's answer:
class MapContainerView:UIView {
#IBOutlet var nonScalingViews: [UIView]!
override var transform: CGAffineTransform {
didSet {
guard let nonScalingViews = nonScalingViews else {
return
}
let invertedTransform = CGAffineTransformInvert(transform)
for view in nonScalingViews {
view.transform = invertedTransform
}
}
}
}

Draw Over Image

I'm working on some drawing code. I have that portion working great.
I want to draw over an image, but I want to still be able to see the detail of the image, the black lines, etc.
What I am working on is making a transparent UIImageView that holds the image.
I'm not sure how to get this set up properly though.
Should this be added above the other UIImageView that I color on or below it?
Here's what I have so far:
- (void)viewDidLoad
{
[super viewDidLoad];
topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 46, 320, 370)];
[topImageView setImage:[UIImage imageNamed:#"imagesmall.png"]];
topImageView.alpha = 1.0;
topImageView.layer.opacity = 1.0;
topImageView.layer.opaque = NO;
[self.view addSubview:topImageView];
[topImageView release];
}
Thoughts anyone?
Yes, you can draw views over other views. They are drawn in the order that they're added as subviews, unless you reorder them after that.
You may need to set the opaque property for some views (this is distinct from and overrides their layer opacity), and set their backgroundColor to nil. UIImageView seems to be transparent by default, as long as its image is; some other UIView subclasses are not.
So, just what is your overlay going to be? If you just need to display one image over another, what you have here seems to work already. If you need to draw some lines programmatically, you'll need to do this:
Create a subclass of UIView.
Implement its drawRect method to display the content you need.
When you add your custom view on top of the background image, make sure it is not opaque and has no backgroundColor.
A common problem here is to find that your foreground is working, but the background isn't being loaded properly. To make sure the background is there, set the alpha of the foreground view to 0.5. You won't want to do that in production, but it will allow you to verify that both views exist.

Setting background color of UIView subclass doesn't work

I'm trying to change background color of one of my UIView subclasses. For some reason self.backgroundColor = [UIColor whiteColor];doesn't do anything when I put it in my - (id)initWithFrame:(CGRect)framemethod inside the view. The view is always black. I have also tried self.myView.backgroundColor ... from my view's controller, but that didn't work either. Any ideas on what I'm doing wrong?
The relevant code looks like this:
[...]
#interface PaperView : UIView
[....]
[...]
#implementation PaperView
[...]
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[...]
// Initialization code
self.backgroundColor = [UIColor whiteColor]; // This doesn't do anything, the view is always black.
}
return self;
}
If this view is being unarchived from a xib, you need to override -initWithCoder:. -initWithFrame: is only invoked if you are creating your view programmatically.
I had this same problem. I hooked into layoutSubviews() and it worked ok:
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.clearColor()
}
This is indicative of the view not having a frame set to it. I recommend setting a breakpoint in your initWithFrame: to verify that its being called. If you were to call, say, ... = [UIView alloc] init], then that could be the source of your problem.
EDIT
If initWithFrame: is in fact being called, it's possible that the view is being covered by another view giving the appearance that it's not working (since you don't see it) or that the view itself is hidden.
Another way to troubleshoot is to override the backgroundColor property and set a breakpoint. Find out what else, in the callstack, is changing the color.
You may use self.layer.backgroundColor instead:
mySubclassedView.layer.backgroundColor = UIColor.green.cgColor
In what method do you call self.myView.backgroundColor?
Are you sure, that it's after viewDidLoad: ?
But, have no idea what is wrong with your first method.
Could you show more code?
I just had this exact same problem. The background color did not show even though I set the correct frame and set the background color to white in my custom init method, as well as in my viewWillAppear method. I also verified that nothing was covering it.
Then I found the solution: I set the background color in viewDidAppear instead, and all was fine:
- (void)viewDidAppear:(BOOL)animated
{
[self.view setBackgroundColor:[UIColor whiteColor]];
self.view.frame = _viewFrame;
}
(The _viewFrame CGRect was passed in to my init method.)
Another option is to set it in - (void)viewDidLayoutSubviews, depending on when and how exactly you want to set your background color.
To be entirely honest, I don't understand (yet) why setting the background color in viewDidAppear worked while it didn't work in the init method, and the code for setting it was identical in both places.
Hope this helps,
Erik
UPDATE:
It does have something to do with view's frame. When I set my view's frame in my init method, then setting the background color in the viewDidAppear no longer has the desired effect. This is even the case if I set my view's frame after my view build method that creates the sub views. So the real mystery is: between the point where I am done creating my view and the point where it is displayed, what in the view's life cycle is causing the view's frame to be reset to something that is incorrect?
So, the answer really is: it will work as long as your frame is set correctly and your view is visible. Just check your view's frame throughout the view's lifecycle to make sure it's correct.
Tricky...
Why can't you implement self.backgroundColor = [UIColor whiteColor] in -viewDidLoad method instead of -initWithFrame? Then try self.backgroundColor = [UIColor whiteColor]; as well as self.myView.backgroundColor to see which works.

Why is this UIImageView autocentering itself?

I have a UIImageView in a UIScrollView in another UIScrollView (based on Apple's
PhotoScroller sample code). When the UIScrollView calls back to its controller to dismiss itself, it calls this method:
- (void)dismiss {
[scrollView removeFromSuperview];
ImageScrollView *isv = [self currentImageScrollView];
UIImage *image = isv.imageView;
image.frame = [self.view convertRect:image.frame fromView:isv];
[self.view insertSubview:image belowSubview:captionView];
[(NSObject *)delegate performSelector:#selector(scrollViewDidClose:)
withObject:self
afterDelay:2.0];
}
Now here's the weird part: the image view jumps to a different position right after this method executes, but before the scollViewDidClose method gets called on the delegate. If the image is larger than its new super view, it jumps so that its left edge is aligned with the left edge of its super view. If it's smaller than its new super view, it jumps to the very center of the view. There is no animation to this change.
So my question is, how do I prevent it from doing that? I've tweaked both the super view (self.view) class and the image view class to see what methods might be called. Neither the frame nor the center is set on the image view after this method is called, and while the layoutSubviews method is called on the super view, that is not what jumps the image to the center or left side of the superview. I've also tried turning off autoResizesSubviews in the super view, and setting the autoresizingMask of the image view to UIViewAutoresizingNone, with no change in behavior.
So where is this happening, and why? And more importantly, how do I make it stop?
I've been beating my head on this for days. Any pointers or guidance would be greatly appreciated.
ImageScrollView is the one centering your UIImageView. Set a breakpoint in ImageScrollView layoutSubviews and you'll see how your UIImageView is being centered.
You're taking ImageScrollView's internal imageView and placing it into another view. That's not going to work because ImageScrollView still retains ownership of that UIImageView instance and is still managing its layout.
You'll either need to copy the image into another UIImageView instance, or you'll need to change ImageScrollView to allow it to relinquish ownership of its imageView.
You're not setting up the frame of the 'image' view when you insert it as a subview. You probably want to do that explicitly if you want the view to appear at a particular position in the scroll view.