setting self of UIView background color - objective-c

i'm trying to do this from inside the .m of a custom view class that is not being loaded from the XIB, but rather programmatically:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor=[UIColor redcolor];
}
return self;
}
i have the same result whether i put the background color in the initWithFrame or other methods. the background color property doesn't take. from the controller, which owns this custom view, i can set the background color fine, with:
self.mycustomview.backgroundColor=[UIColor redcolor];
But I'd like to do this from within the custom view itself, keep stuff like this independent. both the controller and the custom view import UIKit.
I also tried this, which was available from Code Sense:
self.View.backgroundColor=[UIColor redcolor];
but that doesn't work either. i tried both view and View here. I'm sure I'm overlooking something very obvious.
in the view controller i have this, and it works fine. the custom view is called "mapButtons.h":
- (void)viewDidLoad
{
CGRect frame=CGRectMake(0, 0, 320, 460);
self.mapButtons=[[mapButtons alloc] initWithFrame:frame];
self.mapButtons.backgroundColor=[UIColor redColor];
[self.view addSubview:self.mapButtons];
the .h of the custom view is this:
#import <UIKit/UIKit.h>
#interface mapButtons : UIView

If your view is getting created from a XIB (i.e. you added it to some other view using Interface Builder), -initWithFrame: is not going to get called. An object being loaded from a XIB receives -initWithCoder: instead. Try this:
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if(self)
{
self.backgroundColor = [UIColor redColor];
}
return self;
}

I have tested again and this is the full source for what I am doing that works
// MapButtons.h
#import <UIKit/UIKit.h>
// As a note you normally define class names starting with a capital letter
// but I did test this with mapButtons as you had it
#interface MapButtons : UIView
#end
// MapButtons.m
#import "mapButtons.h"
#implementation mapButtons
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor redColor];
}
return self;
}
#end
// TestAppDelegate.m
#implementation TestAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MapButtons *view = [[MapButtons alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window addSubview:view];
[self.window makeKeyAndVisible];
return YES;
}
The fact that xcode is not auto completing is odd but I seem to have this issue intermittently so I have no real solution. People sometimes suggest deleting the projects derived data and restarting xcode.

Related

UIViewController viewDidLayoutSubviews called when transforming a subview

I have the following use case:
a view controller (RotationVC) that contains a bit of logic, among others, rotating one of the subviews (by setting its transform to CGAffineTransformMakeRotation)
this RotationVC overrides the viewDidLayoutSubviews method to set its subviews to be as big as the parent view (self.view)
(creating the view controllers and its views is all done programmatically and it has to stay this way; I tried using autoresizingMask with flexible width and flexible height on the subviews but it didn't work)
another view controller (ParentVC) adds the RotationVC as its child, adds its view as its subview and sets its frame at some point to be square - the idea is that when the frame is set, RotationVC viewDidLayoutSubviews will be called and set the subviews to correct sizes (which actually works fine), and then all works fine
(the RotationVC will be used in other view controllers as well and will have different sized, that's the reason it has been extracted to a 'child' view controller and its size should be flexible)
Well, it doesn't work correctly. The problem is that after each rotation, RotationVC viewDidLayoutSubviews method is called, which sets the rotated views frame. This is wrong, as according to Apple's documentation, using the frame property is wrong when the view's transform is anything else than identity (as is in this case). As a result, the rotated view is 'skewed'. Here is the source code down to the delegate, you should be able to paste it to a single file and run. In case you do, what I'm trying to achieve is for the rotated subview to preserve its shape (in this example it's a rectangle):
#import "AppDelegate.h"
#interface RotationVC : UIViewController
#property (nonatomic, weak) UIView *background;
#property (nonatomic, weak) UIView *foreground;
#property (nonatomic) int degrees;
#end
static const double DURATION = 0.5;
#implementation RotationVC
- (void)viewDidLoad {
[super viewDidLoad];
UIView *tmp = [UIView new];
self.background = tmp;
self.background.layer.borderWidth = 2;
self.background.layer.borderColor = [UIColor redColor].CGColor;
[self.view addSubview:self.background];
tmp = [UIView new];
self.foreground = tmp;
self.foreground.layer.borderWidth = 2;
self.foreground.layer.borderColor = [UIColor blueColor].CGColor;
[self.view addSubview:self.foreground];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.background.frame = self.view.bounds;
CGAffineTransform tmp = self.foreground.transform;
self.foreground.transform = CGAffineTransformIdentity;
self.foreground.frame = self.view.bounds;
self.foreground.transform = tmp;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self performSelector:#selector(rotate) withObject:self];
}
- (void)rotate {
self.degrees = (self.degrees + 15) % 360;
[UIView animateWithDuration:DURATION
delay:0
options:UIViewAnimationOptionCurveLinear|UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.foreground.transform = CGAffineTransformMakeRotation(self.degrees * M_PI/180);
}
completion:^(BOOL finished) {
[self performSelector:_cmd withObject:self afterDelay:0];
}];
}
#end
#interface ParentVC : UIViewController
#property (nonatomic, weak) RotationVC *rotationVC;
#end
#implementation ParentVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
RotationVC *tmp = [RotationVC new];
self.rotationVC = tmp;
[self addChildViewController:self.rotationVC];
[self.rotationVC didMoveToParentViewController:self];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.rotationVC.view];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.rotationVC.view.bounds = CGRectMake(0, 0, 100, 100);
self.rotationVC.view.center = self.view.center;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:[ParentVC new]];
navi.navigationBarHidden = YES;
self.window.rootViewController = navi;
[self.window makeKeyAndVisible];
return YES;
}
#end
There is a hack that works - in the viewDidLayoutSubviews method, store the rotated view's transform to a temp variable, set the view's transform to identity, set the frame, and set it to rotation again - this is ugly as hell and I bet there has to be a better way. I was also able to devise a few other workarounds, but each one was uglier than the previous one...
Would anybody have a hint as to what to do in this case?
I was able to solve the issue. Just use the following code:
self.foreground.bounds = self.view.bounds;
self.foreground.center = self.background.center;
instead of setting the frame.
This is explained in https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW7:
Important: If a view’s transform property is not the identity transform, the value of that view’s frame property is undefined and must be ignored. When applying transforms to a view, you must use the view’s bounds and center properties to get the size and position of the view. The frame rectangles of any subviews are still valid because they are relative to the view’s bounds.
I always thought that setting the frame is setting the bounds and the center at the same time, but apparently this is very untrue.

How can I redraw an image in an custom NSView for an NSStatusItem?

I am having some troubles understanding how to wire a custom NSView for an NSMenuItem to support both animation and dragging and dropping. I have the following subclass of NSView handling the bulk of the job. It draws my icon when the application launches correctly, but I have been unable to correctly setup the subview to change when I invoke the setIcon function from another caller. Is there some element of the design that I am missing?
TrayIconView.m
#import "TrayIconView.h"
#implementation TrayIconView
#synthesize statusItem;
static NSImageView *_imageView;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
statusItem = nil;
isMenuVisible = NO;
_imageView = [[NSImageView alloc] initWithFrame:[self bounds]];
[self addSubview:_imageView];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Draw status bar background, highlighted if menu is showing
[statusItem drawStatusBarBackgroundInRect:[self bounds]
withHighlight:isMenuVisible];
}
- (void)mouseDown:(NSEvent *)event {
[[self menu] setDelegate:self];
[statusItem popUpStatusItemMenu:[self menu]];
[self setNeedsDisplay:YES];
}
- (void)rightMouseDown:(NSEvent *)event {
// Treat right-click just like left-click
[self mouseDown:event];
}
- (void)menuWillOpen:(NSMenu *)menu {
isMenuVisible = YES;
[self setNeedsDisplay:YES];
}
- (void)menuDidClose:(NSMenu *)menu {
isMenuVisible = NO;
[menu setDelegate:nil];
[self setNeedsDisplay:YES];
}
- (void)setIcon:(NSImage *)icon {
[_imageView setImage:icon];
}
TrayIconView.h
#import <Cocoa/Cocoa.h>
#interface TrayIconView : NSView
{
BOOL isMenuVisible;
}
#property (retain, nonatomic) NSStatusItem *statusItem;
- (void)setIcon:(NSImage *)icon;
#end
The solution to this problem was actually outside of the view detailed here. The caller of the interface was being double instantiated on accident, thus nulling out the reference to the previously created NSView. After correcting that concern the app draws and works just fine.
With regard to dragging, I just implemented a subclass of NSView that implemented the Cocoa draggable protocol and added it as a subview to this parent class. That allows dragging onto the currently established NSRect that contains the menubar icon.

UIGestureRecognizer - Get the reference to the touched UIViewController Instead of its View?

How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;

iOS - Create UIView subclass for rounded rectangle

I'm trying to create & use a very simple UIView subclass for a rectangle with rounded corners. I've created a new class as follows :
RoundedRect.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface RoundedRect : UIView
#end
RoundedRect.m
#import "RoundedRect.h"
#implementation RoundedRect
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[self layer] setCornerRadius:10.0f];
[[self layer] setMasksToBounds:YES];
}
return self;
}
#end
I'm using iOS 5.1 with storyboards and have set the custom class property in the IB inspector window to 'RoundedRect', but when I run the app the rectangle still has square corners. Have I missed something obvious?
Thanks
Jonathan
In iOS 5 and up, there is absolutely no need to subclass - you can do it all from Interface Builder.
Select the UIView you want to modify.
Go to the Identity Inspector.
In "User Defined & Runtime Attributes", add "layer.cornerRadius" in Key Path, Type should be "Number" and whatever setting you require.
Also add 'layer.masksToBounds' as Boolean.
Done! With no subclassing, and all in IB.
The other guys have already answered the question but I would refactor it like this to enable use in nibs and in code
#import "RoundedRect.h"
#implementation RoundedRect
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit;
{
CALayer *layer = self.layer;
layer.cornerRadius = 10.0f;
layer.masksToBounds = YES;
}
#end
The initWithFrame method is not called when the view is instantiated from a XIB file. Instead, the initWithCoder: initializer is called, so you need to perform the same initialization in this method.
For views that are loaded from a NIB file, the designated initializer is initWithCoder:. initWithFame: is not called in this case.
If UIView load from Nib, you should use method
- (void)awakeFromNib

Subclass of UIViewController shows black screen

I created a Subclass of UIViewController in my Project and linked it to a View which is modal-pushed by the "RootViewController". I made absolutely no changes to the derived class, but when the "SecondView" is pushed it turns black every time. If i link that view to the standard UIViewController class everything works fine?
Since the "SecondViewController" is derived from UIViewController I can only guess that the Problem has to do with the alloc/init function but I have no idea where to start.
I can provide the sample code I have in front of me now if necessary.
This is the derived subclass:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self; }
- (void)loadView {}
- (void)viewDidLoad {
[super viewDidLoad]; }
- (void)viewDidUnload {
[super viewDidUnload];}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait); }
#end
Header:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#end
- (void)loadView
{
// If you create your views manually, you MUST override this method and use it to create your views.
// If you use Interface Builder to create your views, then you must NOT override this method.
}
FYI, the comment is automatically generated too.
Change this:
- (void)loadView
{
// If you create your views manually, you MUST override this method and use it to create your views.
// If you use Interface Builder to create your views, then you must NOT override this method.
}
To this:
- (void)loadView
{
[super loadView];
// If you create your views manually, you MUST override this method and use it to create your views.
// If you use Interface Builder to create your views, then you must NOT override this method.
}
And it will work.
Try this
-(void)loadView
{
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[view setBackgroundColor:[UIColor whiteColor]];
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10, 50, 100, 100)];
[label setText:#"HAI"];
[view addSubview:label];
self.view = view;
}
If you are trying to get the view to load before it "shows" up during the segue from IB you might want to try something like this in the destination view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view layoutIfNeeded];
}