When can I start using properties set using UIAppearance? - ios

I have some custom appearance properties in my view class (a descendant of UIView). I want to customize the view appearance according to these properties, but I can’t do that inside the initializer, since the values set using [[MyClass appearance] setFoo:…] aren’t in effect at that point:
#interface View : UIView
#property(strong) UIColor *someColor UI_APPEARANCE_SELECTOR;
#end
#implementation View
#synthesize someColor;
// Somewhere in other code before the initializer is called:
// [[View appearance] setSomeColor:[UIColor blackColor]];
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", someColor); // nil
return self;
}
#end
They are already set in layoutSubviews, but that’s not a good point to perform the view customizations, since some customizations may trigger layoutSubviews again, leading to an endless loop.
So, what’s a good point to perform the customizations? Or is there a way to trigger the code that applies the appearance values?

One possible workaround is to grab the value directly from the proxy:
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
NSLog(#"%#", [[View appearance] someColor); // not nil
return self;
}
Of course this kills the option to vary the appearance according to the view container and is generally ugly. Second option I found is to perform the customizations in the setter:
- (void) setSomeColor: (UIColor*) newColor
{
someColor = newColor;
// do whatever is needed
}
Still I’d rather have some hook that gets called after the appearance properties are set.

Why not wait until
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview) {
... code here ...
}
}
if it's giving you trouble?

I believe UIAppearance properties are applied to a view when it is being added into a view hierarchy. So presumably you could access the set properties in UIView didMoveToSuperview.

Caveat: I am using Swift 2, so not sure about earlier versions of Swift / Objective-C. But I have found that didMoveToSuperview() will not work. The properties are available in layoutSubviews(), but that's not a great place to do anything like this (since it can be called more than once). The best place to access these properties in the lifeCycle of the view I have found is didMoveToWindow().

I would have thought that viewDidLoad would be best if it's a one-time thing. Otherwise, viewWillAppear.
EDIT:
If you want to do it in the view, and not it's controller then I would create a custom init for the view along the lines of:
-(id) initWithFrame:(CGRect) frame andAppearanceColor:(UIColor)theColor;
thereby passing the colour into the view at creation time.

Related

Implementing UIScrollView Methods when Subclassing UITextView

I'm having problems controlling the scrolling within a UITextView that I'm using, so I've opted to create my own subclass.
I've got a very basic question about providing implementations for some of the UIScrollView superclass methods.
Here's my skeleton code for the UITextView subclass:
#interface PastedTextView : UITextView
#end
#implementation PastedTextView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"scrollRectToVisible");
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
NSLog(#"setContentOffset");
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"zoomToRect");
}
#end
When will those UIScrollView methods be called? Only from my own client code? Or will they be called by the framework?
Update:
The reason I've asked this is because I'm having the following problem: I'm programatically adding text to the UITextView (from the pasteboard). When I do so, if the textview has scrolled such that the top of the content is no longer in view, the text view scrolls back to the top after the new text has been appended.
I'm not explicitly triggering this scroll, so it's happening within the framework.
I haven't found anything in Apple's documentation that describes this behaviour. So, I've been trying to locate the source of the scrolling so that I can avoid it...
When this scroll happens, none of the above methods are called. Incidentally, neither is UITextViews scrollRangeToVisible method (I've tried adding that method to the subclass implementation). I can't figure out why that implicit scroll back to the top is happening and I want to prevent it...
If you override these UIScrollView methods as shown, any caller (be it your code, or the system's) will hit your implementation instead of the builtin UIScrollView one. If you want to take advantage of the system implementation, you can always call super.
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[super zoomToRect:rect animated:animated];
NSLog(#"zoomToRect");
}

Why 'NSInternalInconsistencyException' happens when overwrite 'initWithCoder' method

I came across the exception 'NSInternalInconsistencyException' when I write the code below in a viewcontroller which is associated with storyboard:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.view.backgroundColor = [UIColor blackColor];
}
return self;
}
However, if I delete the line 'self.view.backgroundColor = [UIColor blackColor];', there is no problem. I don`t know the reason.
Actually, in the stroyboard I have several controllers, and I want to make a base controller for all the controllers. The purpose is that I want to set a background view(UIView) for them.
I try to add a UIView in the viewDidLoad method of the basecontroller, but the view covers any components that set in the storyboard. I think the reason is that the components on the sub viewcontrollers are initialized in the 'initWithCoder' method before the 'ViewDidLoad' method is called. So I try to add the bgView in the 'initWithCoder' method of the basecontroller, and this led to the problem above.
So I also want to know a right way to achieve my purpose. Thanks!
dont set graphics related properties in init and bring subview to front. Set them from
-(void)awakeFromNib
{
[self.view.superview bringSubviewToFront:self.view];
self.view.backgroundColor = [UIColor blackColor];
}

UIView subclass in XIB doesn't display (drawRect: never called)

I am designing a custom view controller in interface builder with a XIB file and I have a custom UIView subclass that I want to add to my view controller. Here is how I've put my custom view into the view controller:
My TonerEffectButtonView class is a subclass of UIView (and has nothing to do with UIButton) and here is my code for it:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)prepareWithSelector:(SEL)selector onTarget:(id)targ withFilter:(GPUImageFilter*)filter{
self.gpuImageView = [[GPUImageView alloc] initWithFrame:self.frame];
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:targ action:selector];
[self addGestureRecognizer:gesture];
selectedFilter = filter;
[filter addTarget:self.gpuImageView];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
}
initWithFrame: is not called anyway as I'm creating the view through a XIB, and I don't need any customization in initWithCoder: so I haven't implemented it. I have a GPUImageView (that is a part of a library that I'm using) that I'm creating programatically inside my view, and adding camera input to it (if you ask what happens in the prepare method). Prepare method is called from outside, and I've verified my UIView's frame is correct when it is called. I have a breakpoint on [super drawRect:rect]; and it never gets called, and when I run the program, my custom view is not displayed, nor does receive touch events (but prepare method DOES get called so the instance is created with the correct frame). It's like it has never been put there in interface builder. What could be the cause of this? I've seen many posts about this, and they mostly refer to creating views programatically and indicating problems with 'initWithFrame:'. But I have nothing to do with that method, and I want to use the interface builder/XIB couple, please don't advice me to create the view controller programatically. What could be the cause of it?
Thanks,
Can.
UPDATE: I can verify that -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx is also not called in any way.
Ok, I've found the answer. First, I've tried to add another instance programatically, and I've found out that it has autoresizing mask set to none, whereas my original instance has it set to W+H. This clue made me realize that in fact, I was forgetting to add my GPUImageView instance as a subview into my custom view. I've added it, and it worked.

Setting the properties of items in an customview in iOS

I am creating a customView and for example, would like to set the text color of the label and initialise the text of the label.
The initWithFrame is the generic function.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
I have tried initialising the label in the initWithFrame, but it doesn't work. But when I do it in awakeFromNib, it allows me to set the text color but not the text(this value comes from a dictionary)
-(void)awakeFromNib
{
//setting of the label textcolor
}
What would be the correct way to initialise the color and text of labels and other stuff?
Need some suggestions and guidance...
Edit:
-(void)viewDidLoad
{
[self updateViewWithDictionary:dictPassed];
}
Something like this?
What I do in some project's is expose a public method like so:
- (void)updateViewWithDictionary:(NSDictionary *)confDictionary;
In that dictionary I pass the parameters I want, and inside the UIView's subview I update it according to what I want.
Edit 1:
Read wrongly your question, sorry. You have a custom UIView that you would like to be updated when your UIViewController starts, or when you actually use it. So you should have something like this:
#interface MyView : UIView
{
}
- (void)updateViewWithDictionary:(NSDictionary *)confDictionary;
#end
And from your viewDidLoad:
-(void)viewDidLoad
{
[self.customView updateViewWithDictionary:dictPassed];
}

Simple UIView drawRect not being called

I can't figure out what the problem is here. I have a very simple UIViewController with a very simple viewDidLoad method:
-(void)viewDidLoad {
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
[self.view addSubview:v];
[super viewDidLoad];
}
And my GameView is initialized as follows:
#interface GameView : UIView {
and it simply has a new drawRect method:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
NSLog(#"drawing");
}
In my console, I see "making game view" being printed, but "drawing" never is printed. Why? Why isn't my drawRect method in my custom UIView being called. I'm literally just trying to draw a circle on the screen.
Have you tried specifying the frame in the initialization of the view? Because you are creating a custom UIView, you need to specify the frame for the view before the drawing method is called.
Try changing your viewDidLoad to the following:
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
if (v == nil)
NSLog(#"was not allocated and/or initialized");
[self.view addSubview:v];
if (v.superview == nil)
NSLog(#"was not added to view");
[super viewDidLoad];
let me know what you get.
Check if your view is being displayed. If a view is not currently on screen, drawRect will not be getting called even if you add the view to its superview. A possibility is that your view is blocked by the some other view.
And as far as I know, you don't need to write [super drawRect];
Note that even if viewDidLoad is called on a view controller it doesn't necessarily indicate the view controller's view is displayed on screen. Example: Assume a view controller A has an ivar where a view controller B is stored and view controller A's view is currently displayed. Also assume B is alloced and inited. Now if some method in A causes B's view to be accessed viewDidLoad in B will be called as a result regardless whether it's displayed.
If you're using a CocoaTouch lib or file you may need to override the initWithCoder method instead of viewDidLoad.
Objective-C:
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
//Do Stuff Here
}
return self;
}
I had a view that was offscreen, that I would load onscreen and then redraw. However, while cleaning up auto-layout constraints in XCode, it decided my offscreen view should have a frame (0,0,0,0) (x,y,w,h). And with a (0,0) size, the view would never load.
Make sure your NSView has a nonzero frame size, or else drawRect will not be called.

Resources