I'm playing with NSGestureRecognizer(s) in Objective-C.
I have a simple Custom View in the XIB canvas to which I applied the press, pan, magnify, and rotate gesture recognisers. From each one I have created an action in AppDelegate.m and then added code to it. They all work to some extent but the way magnify and rotate behave does not satisfy me.
Here is the code for both of them:
- (IBAction)magnifyView:(NSMagnificationGestureRecognizer *)sender {
CGFloat magnification = sender.magnification + 1.0;
NSView *view = sender.view;
CGAffineTransform transform = CGAffineTransformMakeScale(magnification, magnification);
[[view layer] setAffineTransform:transform];
sender.magnification = 0;
}
and ...
- (IBAction)rotateView:(NSRotationGestureRecognizer *)sender {
CGFloat rotation = sender.rotation;
NSView *view = sender.view;
CGAffineTransform transform = CGAffineTransformMakeRotation(rotation);
[[view layer] setAffineTransform:transform];
sender.rotation = 0;
}
I'm using a 2016 MBP with macOS 12.4 and Xcode 13.4.1 to realise this and, using the trackpad for the gestures, I see that magnifyView seems to stutter unless I strongly open my fingers in an "unpinch" gesture—which risks triggering the macOS show desktop, while rotateView only rotates of a few degrees before coming back.
The whole project can be found here, if you feel like giving it a look. It doesn't contain much else anyway, it's an experiment.
Could you please give it a look (or even just at the methods above) and tell me what I could do to improve it?
Thank you!
Related
What I Want to Do:
In Messages.app on OS 10.10, when you scroll the left-most pane (the list of conversations) upwards, a nice horizontal line fades in over about 0.5 seconds. When you scroll back down, the line fades back out.
What I Have:
I am trying to achieve this effect in my own app and I've gotten very close. I subclassed NSScrollView and have done the following:
- (void) awakeFromNib
{
_topBorderLayer = [[CALayer alloc] init];
CGColorRef bgColor = CGColorCreateGenericGray(0.8, 1.0f);
_topBorderLayer.backgroundColor = bgColor;
CGColorRelease(bgColor);
_topBorderLayer.frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, 1.0f);
_topBorderLayer.autoresizingMask = kCALayerWidthSizable;
_topBorderLayer.zPosition = 1000000000;
_fadeInAnimation = [[CABasicAnimation animationWithKeyPath:#"opacity"] retain];
_fadeInAnimation.duration = 0.6f;
_fadeInAnimation.fromValue = #0;
_fadeInAnimation.toValue = #1;
_fadeInAnimation.removedOnCompletion = YES;
_fadeInAnimation.fillMode = kCAFillModeBoth;
[self.layer insertSublayer:_topBorderLayer atIndex:0];
}
- (void) layoutSublayersOfLayer:(CALayer *)layer
{
NSPoint origin = [self.contentView documentVisibleRect].origin;
// 10 is a fudge factor for blank space above first row's actual content
if (origin.y > 10)
{
if (!_topBorderIsShowing)
{
_topBorderIsShowing = YES;
[_topBorderLayer addAnimation:_fadeInAnimation forKey:nil];
_topBorderLayer.opacity = 1.0f;
}
}
else
{
if (!_topBorderIsShowing)
{
_topBorderIsShowing = NO;
// Fade out animation here; omitted for brevity
}
}
}
The Problem
The "border" sublayer that I add is not drawing over top of all other content in the ScrollView, so that we end up with this:
The frames around the image, textfield and checkbox in this row of my outlineView are "overdrawing" my border layer.
What Causes This
I THINK this is because the scrollView is contained inside an NSVisualEffectView that has Vibrancy enabled. The reason I think this is that if I change the color of my "border" sublayer to 100% black, this issue disappears. Likewise, if I turn on "Reduce Transparency" in OS X's System Preferences > Accessibility, the issue disappears.
I think the Vibrancy compositing is taking my grey border sublayer and the layers that represent each of those components in the outlineView row and mucking up the colors.
So... how do I stop that for a single layer? I've tried all sorts of things to overcome this. I feel like I'm 99% of the way to a solid implementation, but can't fix this last issue. Can anyone help?
NB:
I am aware that it's dangerous to muck directly with layers in a layer-backed environment. Apple's docs make it clear that we can't change certain properties of a view's layer if we're using layer-backing. However: adding and removing sublayers (as I am) is not a prohibited action.
Update:
This answer, while it works, causes problems if you're using AutoLayout. You'll start to get warnings that the scrollView still needs update after calling Layout because something dirtied the layout in the middle of updating. I have not been able to find a workaround for that, yet.
Original solution:
Easiest way to fix the problem is just to inset the contentView by the height of the border sublayer with this:
- (void) tile
{
id contentView = [self contentView];
[super tile];
[contentView setFrame:NSInsetRect([contentView frame], 0.0, 1.0)];
}
Should have thought of it hours ago. Works great. I'll leave the question for anyone who might be looking to implement these nice fading-borders.
I'm currently trying to make a window that looks like the Volume OS X window:
To make this, I have my own NSWindow (using a custom subclass), which is transparent/titlebar-less/shadow-less, that has a NSVisualEffectView inside its contentView. Here's the code of my subclass to make the content view round:
- (void)setContentView:(NSView *)aView {
aView.wantsLayer = YES;
aView.layer.frame = aView.frame;
aView.layer.cornerRadius = 14.0;
aView.layer.masksToBounds = YES;
[super setContentView:aView];
}
And here's the outcome (as you can see, the corners are grainy, OS X's are way smoother):
Any ideas on how to make the corners smoother? Thanks
Update for OS X El Capitan
The hack I described in my original answer below is not needed on OS X El Capitan anymore. The NSVisualEffectView’s maskImage should work correctly there, if the NSWindow’s contentView is set to be the NSVisualEffectView (it’s not enough if it is a subview of the contentView).
Here’s a sample project: https://github.com/marcomasser/OverlayTest
Original Answer – Only Relevant for OS X Yosemite
I found a way to do this by overriding a private NSWindow method: - (NSImage *)_cornerMask. Simply return an image created by drawing an NSBezierPath with a rounded rect in it to get a look similar to OS X’s volume window.
In my testing I found that you need to use a mask image for the NSVisualEffectView and the NSWindow. In your code, you’re using the view’s layer’s cornerRadius property to get the rounded corners, but you can achieve the same by using a mask image. In my code, I generate an NSImage that is used by both the NSVisualEffectView and the NSWindow:
func maskImage(#cornerRadius: CGFloat) -> NSImage {
let edgeLength = 2.0 * cornerRadius + 1.0
let maskImage = NSImage(size: NSSize(width: edgeLength, height: edgeLength), flipped: false) { rect in
let bezierPath = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
NSColor.blackColor().set()
bezierPath.fill()
return true
}
maskImage.capInsets = NSEdgeInsets(top: cornerRadius, left: cornerRadius, bottom: cornerRadius, right: cornerRadius)
maskImage.resizingMode = .Stretch
return maskImage
}
I then created an NSWindow subclass that has a setter for the mask image:
class MaskedWindow : NSWindow {
/// Just in case Apple decides to make `_cornerMask` public and remove the underscore prefix,
/// we name the property `cornerMask`.
#objc dynamic var cornerMask: NSImage?
/// This private method is called by AppKit and should return a mask image that is used to
/// specify which parts of the window are transparent. This works much better than letting
/// the window figure it out by itself using the content view's shape because the latter
/// method makes rounded corners appear jagged while using `_cornerMask` respects any
/// anti-aliasing in the mask image.
#objc dynamic func _cornerMask() -> NSImage? {
return cornerMask
}
}
Then, in my NSWindowController subclass I set up the mask image for the view and the window:
class OverlayWindowController : NSWindowController {
#IBOutlet weak var visualEffectView: NSVisualEffectView!
override func windowDidLoad() {
super.windowDidLoad()
let maskImage = maskImage(cornerRadius: 18.0)
visualEffectView.maskImage = maskImage
if let window = window as? MaskedWindow {
window.cornerMask = maskImage
}
}
}
I don’t know what Apple will do if you submit an app with that code to the App Store. You’re not actually calling any private API, you’re just overriding a method that happens to have the same name as a private method in AppKit. How should you know that there’s a naming conflict? 😉
Besides, this fails gracefully without you having to do anything. If Apple changes the way this works internally and the method just won’t get called, your window does not get the nice rounded corners, but everything still works and looks almost the same.
If you’re curious about how I found out about this method:
I knew that the OS X volume indication did what I want to do and I hoped that changing the volume like a madman resulted in noticeable CPU usage by the process that puts that volume indication on screen. I therefore opened Activity Monitor, sorted by CPU usage, activated the filter to only show “My Processes” and hammered my volume up/down keys.
It became clear that coreaudiod and something called BezelUIServer in /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer did something. From looking at the bundle resources for the latter, it was evident that it is responsible for drawing the volume indication. (Note: that process only runs for a short time after it displays something.)
I then used Xcode to attach to that process as soon as it launched (Debug > Attach to Process > By Process Identifier (PID) or Name…, then enter “BezelUIServer”) and changed the volume again. After the debugger was attached, the view debugger let me take a look at the view hierarchy and see that the window was an instance of a NSWindow subclass called BSUIRoundWindow.
Using class-dump on the binary showed that this class is a direct descendant of NSWindow and only implements three methods, whereas one is - (id)_cornerMask, which sounded promising.
Back in Xcode, I used the Object Inspector (right hand side, third tab) to get the address for the window object. Using that pointer I checked what this _cornerMask actually returns by printing its description in lldb:
(lldb) po [0x108500110 _cornerMask]
<NSImage 0x608000070300 Size={37, 37} Reps=(
"NSCustomImageRep 0x608000082d50 Size={37, 37} ColorSpace=NSCalibratedRGBColorSpace BPS=0 Pixels=0x0 Alpha=NO"
)>
This shows that the return value actually is an NSImage, which is the information I needed to implement _cornerMask.
If you want to take a look at that image, you can write it to a file:
(lldb) e (BOOL)[[[0x108500110 _cornerMask] TIFFRepresentation] writeToFile:(id)[#"~/Desktop/maskImage.tiff" stringByExpandingTildeInPath] atomically:YES]
To dig a bit deeper, you can use Hopper Disassembler to disassemble BezelUIServer and AppKit and generate pseudo code to see how the _cornerMask is implemented and used to get a clearer picture of how the internals work. Unfortunately, everything in regard to this mechanism is private API.
I remember doing this sort of thing long before CALayer was around. You use NSBezierPath to make the path.
I don't believe you actually need to subclass NSWindow. The important bit about the window is to initialize the window with NSBorderlessWindowMask and apply the following settings:
[window setAlphaValue:0.5]; // whatever your desired opacity is
[window setOpaque:NO];
[window setHasShadow:NO];
Then you set the contentView of your window to a custom NSView subclass with the drawRect: method overridden similar to this:
// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);
// make a rounded rect and fill it with whatever color you like
NSBezierPath* clipPath = [NSBezierPath bezierPathWithRoundedRect:self.frame xRadius:14.0 yRadius:14.0];
[[NSColor blackColor] set]; // your bg color
[clipPath fill];
result (ignore the slider):
Edit: If this method is for whatever reason undesirable, can you not simply assign a CAShapeLayer as your contentView's layer then either convert the above NSBezierPath to CGPath or just construct as a CGPath and assign the path to the layers path?
The "smooth effect" you are referring to is called "Antialiasing". I did a bit of googling and I think you might be the first person who has tried to round the corners of an NSVisualEffectView. You told the CALayer to have a border radius, which will round the corners, but you didn't set any other options. I would try this:
layer.shouldRasterize = YES;
layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;
Anti-alias diagonal edges of CALayer
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/occ/instp/CALayer/edgeAntialiasingMask
Despite the limitations of NSVisualEffectView not antialiasing edges, here's a kludgey workaround for now that should work for this application of a floating title-less unresizeable window with no shadow - have a child window underneath that draws out just the edges.
I was able to get mine to look like this:
by doing the following:
In a controller holding everything:
- (void) create {
NSRect windowRect = NSMakeRect(100.0, 100.0, 200.0, 200.0);
NSRect behindWindowRect = NSMakeRect(99.0, 99.0, 202.0, 202.0);
NSRect behindViewRect = NSMakeRect(0.0, 0.0, 202.0, 202.0);
NSRect viewRect = NSMakeRect(0.0, 0.0, 200.0, 200.0);
window = [FloatingWindow createWindow:windowRect];
behindAntialiasWindow = [FloatingWindow createWindow:behindWindowRect];
roundedHollowView = [[RoundedHollowView alloc] initWithFrame:behindViewRect];
[behindAntialiasWindow setContentView:roundedHollowView];
[window addChildWindow:behindAntialiasWindow ordered:NSWindowBelow];
backingView = [[NSView alloc] initWithFrame:viewRect];
contentView = [[NSVisualEffectView alloc] initWithFrame:viewRect];
[contentView setWantsLayer:NO];
[contentView setState:NSVisualEffectStateActive];
[contentView setAppearance:
[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
[contentView setMaskImage:[AppDelegate maskImageWithBounds:contentView.bounds]];
[backingView addSubview:contentView];
[window setContentView:backingView];
[window setLevel:NSFloatingWindowLevel];
[window orderFront:self];
}
+ (NSImage *) maskImageWithBounds: (NSRect) bounds
{
return [NSImage imageWithSize:bounds.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:20.0 yRadius:20.0];
[path setLineJoinStyle:NSRoundLineJoinStyle];
[path fill];
return YES;
}];
}
RoundedHollowView's drawrect looks like this:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);
[[NSColor colorWithDeviceWhite:1.0 alpha:0.7] set];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:20.0 yRadius:20.0];
path.lineWidth = 2.0;
[path stroke];
}
Again, this is a hack and you may need to play with the lineWidth / alpha values depending on the base color you use - in my example if you look really closely or under lighter backgrounds you'll make out the border a bit, but for my own use it feels less jarring than not having any antialiasing.
Keep in mind that the blending mode won't be the same as the native osx yosemite pop-ups like the volume control - those appear to use a different undocumented behindwindow appearance that shows more of a color burn effect.
All kudos to Marco Masser for the most neat solution, there're two useful points:
For smooth rounded corners to work, the NSVisualEffectView must be the root view within view controller.
When using the dark material there are still funny light cropped edges that get very apparent on the dark background. Make your window background transparent to avoid this, window.backgroundColor = NSColor.clearColor().
None of these solutions worked for me on Mojave. However after an hour of research, I found this amazing repo which showcases different window designs. One of the solution looks like the OP's desired look. I tried it and it worked with nicely anti-aliased rounded corners and no titlebar artifact remaining. Here is the working code:
let visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.wantsLayer = true
visualEffect.layer?.cornerRadius = 16.0
window?.titleVisibility = .hidden
window?.styleMask.remove(.titled)
window?.backgroundColor = .clear
window?.isMovableByWindowBackground = true
window?.contentView?.addSubview(visualEffect)
Note at the end the contentView.addSubview(visualEffect) instead of contentView = visualEffect. This is one of the key to make it work.
I have an iPad application that has a base image UIImageView (in this case a large building or site plan or diagram) and then multiple 'pins' can be added on top of the plan (visually similar to Google Maps). These pins are also UIImageViews and are added to the main view on tap gestures. The base image is also added to the main view on viewDidLoad.
I have the base image working with the pinch gesture for zooming but obviously when you zoom the base image all the pins stay in the same x and y coordinates of the main view and loose there relative positioning on the base image (whose x,y and width,height coordinates have changed).
So far i have this...
- (IBAction)planZoom:(UIPinchGestureRecognizer *) recognizer;
{
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
recognizer.scale = 1;
for (ZonePin *pin in planContainer.subviews) {
if ([pin isKindOfClass:[ZonePin class]]){
CGRect pinFrame = pin.frame;
// ****************************************
// code to reposition the pins goes here...
// ****************************************
pin.frame = pinFrame;
}
}
}
I need help to calculate the math to reposition the pins x/y coordinates to retain there relative position on the zoomed in or out plan/diagram. The pins obviously do not want to be scaled/zoomed at all in terms of their width or height - they just need new x and y coordinates that are relative to there initial positions on the plan.
I have tried to work out the math myself but have struggled to work it through and unfortunately am not yet acquainted with the SDK enough to know if there is provision available built in to help or not.
Help with this math related problem would be really appreciated! :)
Many thanks,
Michael.
InNeedOfMathTuition.com
First, you might try embedding your UIImageView in a UIScrollView so zooming is largely accomplished for you. You can then set the max and min scale easily, and you can scroll around the zoomed image as desired (especially if your pins are subviews of the UIImageView or something else inside the UIScrollView).
As for scaling the locations of the pins, I think it would work to store the original x and y coordinates of each pin (i.e. when the view first loads, when they are first positioned, at scale 1.0). Then when the view is zoomed, set x = (originalX * zoomScale) and y = (originalY * zoomScale).
I had the same problem in an iOS app a couple of years ago, and if I recall correctly, that's how I accomplished it.
EDIT: Below is more detail about how I accomplished this (I'm looking my old code now).
I had a UIScrollView as a subview of my main view, and my UIImageView as a subview of that. My buttons were added to the scroll view, and I kept their original locations (at zoom 1.0) stored for reference.
In -(void)scrollViewDidScroll:(UIScrollView *)scrollView method:
for (id element in myButtons)
{
UIButton *theButton = (UIButton *)element;
CGPoint originalPoint = //get original location however you want
[theButton setFrame:CGRectMake(
(originalPoint.x - theButton.frame.size.width / 2) * scrollView.zoomScale,
(originalPoint.y - theButton.frame.size.height / 2) * scrollView.zoomScale,
theButton.frame.size.width, theButton.frame.size.height)];
}
For the -(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView method, I returned my UIImageView. My buttons scaled in size, but I didn't include that in the code above. If you're finding that the pins are scaling in size automatically, you might have to store their original sizes as well as original coordinates and use that in the setFrame call.
UPDATE...
Thanks to 'Mr. Jefferson' help in his answer above, albeit with a differing implementation, I was able to work this one through as follows...
I have a scrollView which has a plan/diagram image as a subview. The scrollView is setup for zooming/panning etc, this includes adding UIScrollViewDelegate to the ViewController.
On user double tapping on the plan/diagram a pin image is added as a subview to the scrollView at the touch point. The pin image is a custom 'ZonePin' class which inherits from UIImageView and has a couple of additional properties including 'baseX' and 'baseY'.
The code for adding the pins...
- (IBAction)planDoubleTap:(UITapGestureRecognizer *) recognizer;
{
UIImage *image = [UIImage imageNamed:#"Pin.png"];
ZonePin *newPin = [[ZonePin alloc] initWithImage:image];
CGPoint touchPoint = [recognizer locationInView:planContainer];
CGFloat placementX = touchPoint.x - (image.size.width / 2);
CGFloat placementY = touchPoint.y - image.size.height;
newPin.frame = CGRectMake(placementX, placementY, image.size.width, image.size.height);
newPin.zoneRef = [NSString stringWithFormat:#"%#%d", #"BF", pinSeq++];
newPin.baseX = placementX;
newPin.baseY = placementY;
[planContainer addSubview:newPin];
}
I then have two functions for handling the scrollView interaction and this handles the scaling/repositioning of the pins relative to the plan image. These methods are as follows...
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return planImage;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
for (ZonePin *pin in planContainer.subviews) {
if ([pin isKindOfClass:[ZonePin class]]){
CGFloat newX, newY;
newX = (pin.baseX * scrollView.zoomScale) + (((pin.frame.size.width * scrollView.zoomScale) - pin.frame.size.width) / 2);
newY = (pin.baseY * scrollView.zoomScale) + ((pin.frame.size.height * scrollView.zoomScale) - pin.frame.size.height);
CGRect pinFrame = pin.frame;
pinFrame.origin.x = newX;
pinFrame.origin.y = newY;
pin.frame = pinFrame;
}
}
}
For reference, the calculations for position the pins, by the nature of them being pins' centres the pin image on the x axis but has the y-axis bottom aligned.
The only thing left for me to do with this is to reverse the calculations used in the scrollViewDidScroll method when I add pins when zoomed in. The code for adding pins above will only work properly when the scrollView.zoomScale is 1.0.
Other than that, it now works great! :)
I've got a custom map view which is made of a UIScrollView. The scroll view's subview is backed by a CATiledLayer. Everything works great here. Panning & zooming loads up new map tiles and everything performs well.
What I want to do is capture frames of video of animations to this scroll view. Essentially, I want to create a video of animated changes to the scroll view's contentOffset and zoomScale.
I know that the concept is sound as I can get the private API function UIGetScreenImage() to capture the app's screen at, say, 10fps, combine these images, and I get playback animations that are smooth and have the timing curves used by the scroll view animations.
My problem, of course, is that I can't use the private API. Going through the alternatives outlined by Apple here leaves me with pretty much one supposedly valid option: asking a CALayer to renderInContext and taking a UIGraphicsGetImageFromCurrentImageContext() from that.
This just doesn't seem to work with CATiledLayer-backed views, though. A blocky, un-zoomed image is what is captured, as if the higher-resolution tiles never load. This somewhat makes sense given that CATiledLayer draws in background threads for performance and calling renderInContext from the main thread might not catch these updates. The result is similar even if I render the tiled layer's presentationLayer as well.
Is there an Apple-sanctioned way of capturing an image of a CATiledLayer-backed view during the course of the containing scroll view's animations? Or at any point, for that matter?
BTW, this is doable if you properly implement renderLayer:inContext: in your CATiledLayer-backed view.
I did a quick test, and using renderInContext: on a view wrapping the scroll view seemed to work. Have you tried that?
This code works for me.
- (UIImage *)snapshotImageWithView:(CCTiledImageScrollView *)view
{
// Try our best to approximate the best tile set zoom scale to use
CGFloat tileScale;
if (view.zoomScale >= 0.5) {
tileScale = 2.0;
}
else if (view.zoomScale >= 0.25) {
tileScale = 1.0;
}
else {
tileScale = 0.5;
}
// Calculate the context translation based on how far zoomed in or out.
CGFloat translationX = -view.contentOffset.x;
CGFloat translationY = -view.contentOffset.y;
if (view.contentSize.width < CGRectGetWidth(view.bounds)) {
CGFloat deltaX = (CGRectGetWidth(view.bounds) - view.contentSize.width) / 2.0;
translationX += deltaX;
}
if (view.contentSize.height < CGRectGetHeight(view.bounds)) {
CGFloat deltaY = (CGRectGetHeight(view.bounds) - view.contentSize.height) / 2.0;
translationY += deltaY;
}
// Pass the tileScale to the context because that will be the scale used in drawRect by your CATiledLayer backed UIView
UIGraphicsBeginImageContextWithOptions(CGSizeMake(CGRectGetWidth(view.bounds) / view.zoomScale, CGRectGetHeight(view.bounds) / view.zoomScale), NO, tileScale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, translationX / view.zoomScale, translationY / view.zoomScale);
// The zoomView is a subview of UIScrollView. The CATiledLayer backed UIView is a subview of the zoomView.
[view.zoomView.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Full sample code found here: https://github.com/gortega56/CCCanvasView
I'd like to "lock" my background image (a UIImageView) such that it doesn't look like it's getting cropped when I rotate my device. It looks correct in landscape view, but doesn't follow along with the rotation.
I've tried stretching it using setAutoresizingMask, but the results look horrible. I've fiddled around with the transform property of the UIImageView, but fail to make it look correct.
Any pointers would be appreciated. I'm not even sure if I'm on the right track.
You can have different images for portrait/landscape orientations and switch between the two with UIView animations on willRotate callbacks. I tried it and the result is quite smooth.
OK, I managed to make it work using the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
...
UIColor *color = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"background.png"]];
self.background.backgroundColor = color;
[color release];
...
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
float angle = (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) ? M_PI_2 : 0.0;
self.background.transform = CGAffineTransformMakeRotation(angle);
}
Bear in mind that background~ipad.png is 1024x768, that is landscape as default. If your image is portrait you need to check for landscape instead of portrait in the conditional cases.