I’m overlaying two UIViews with a white backgroundColor at 25% opacity. In a small part, they overlap each other, meaning that at that area, they are summed to 50% opacity.
I’d like to keep that 25% opacity, even if the two views overlap, effectively meaning that in those overlapped points, each view’s opacity drops to 12.5% to total 25%.
I’ve done a little looking into compositing but I’m not sure which of these modes would help, or how I’d go about applying them to a specific part of these two UIView instances.
(http://docs.oracle.com/javase/tutorial/2d/advanced/compositing.html is what I was reading, and I found the CGBlendMode for drawing, if it comes to using that (though I’d prefer not to if possible!))
You can't control the compositing mode of views (or, really, CALayers) on iOS.
The best solution I can think of here is to leave both views with a clearColor (or nil) background, and use a single CAShapeLayer to draw the background of both. If your two views have the same parent, it's not too hard.
Let's say the parent is of type ParentView. Override layoutSubviews in ParentView to create and update the backdrop layer as necessary. Be sure to send setNeedsLayout to the parent view if you move either of the child views.
ParentView.h
#import <UIKit/UIKit.h>
#interface ParentView : UIView
#property (nonatomic, strong) IBOutlet UIView *childView0;
#property (nonatomic, strong) IBOutlet UIView *childView1;
#end
ParentView.m
#import "ParentView.h"
#implementation ParentView {
CAShapeLayer *backdrop;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutBackdrop];
}
- (void)layoutBackdrop {
[self createBackdropIfNeeded];
[self arrangeBackdropBehindChildren];
[self setBackdropPath];
}
- (void)createBackdropIfNeeded {
if (backdrop == nil) {
backdrop = [CAShapeLayer layer];
backdrop.fillColor = [UIColor colorWithWhite:1 alpha:0.25].CGColor;
backdrop.fillRule = kCAFillRuleNonZero;
backdrop.strokeColor = nil;
}
}
- (void)arrangeBackdropBehindChildren {
[self.layer insertSublayer:backdrop atIndex:0];
}
- (void)setBackdropPath {
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.childView0.frame];
[path appendPath:[UIBezierPath bezierPathWithRect:self.childView1.frame]];
backdrop.path = path.CGPath;
}
#end
If you add them both to the same parent UIView, tell that UIView to rasterize, then set the alpha on the parent, you'll get the desired effect. I'm not sure if that fits with your display structure or performance needs.
UIView *parent = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[parent setBackgroundColor:[UIColor clearColor]];
[parent.layer setShouldRasterize:YES];
[parent.layer setRasterizationScale:[[UIScreen mainScreen] scale]];
[parent setAlpha:0.25];
UIView *subview1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 125, 125)];
[subview1 setBackgroundColor:[UIColor whiteColor]];
[parent addSubview:subview1];
UIView *subview2 = [[UIView alloc] initWithFrame:CGRectMake(75, 75, 125, 125)];
[subview2 setBackgroundColor:[UIColor whiteColor]];
[parent addSubview:subview2];
Related
I have a GameOver UIView that I call from inside my main UIViewController. It is just a 'popover' window that has the text game over, the score, and some blur effects to blur the main UIViewcontroller.
I try to pass an int to the UIView, but it doesn't accept it unless it is in the - (void)drawRect:(CGRect)rect method.
If I move the score label to drawRect method, the label is updated. But the blur effects go away.
What am I doing wrong?
MainViewController.m
#import "GameOverView.h"
#interface ViewController () {
GameOverView * gov;
}
- (void) showGameOver {
gov = [[GameOverView alloc] initWithFrame:self.view.bounds];
NSLog(#"Passing score of: %i", self.score);
gov.finalScore = self.score;
[self.view addSubview:gov];
}
GameOverView.h
#interface GameOverView : UIView {}
#property (nonatomic) int finalScore;
#end
GameOverView.M
#implementation GameOverView
- (id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
//self.backgroundColor = [UIColor redColor];
NSLog(#"Score:%i", self.finalScore );
UIVisualEffect *blurEffect;
blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView;
visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
visualEffectView.frame = super.bounds;
[super addSubview:visualEffectView];
UILabel * lblGameOver = [[UILabel alloc] initWithFrame:CGRectMake(0,0, frame.size.width, 200)];
lblGameOver.center = CGPointMake(frame.size.width/2, 100);
lblGameOver.text = [NSString stringWithFormat: #"GAME OVER %i", self.finalScore];
[self addSubview:lblGameOver];
UIButton * button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 200)];
button.center = CGPointMake(frame.size.width/2, 200);
[button setTitle:#"Start New Game" forState:UIControlStateNormal];
[button addTarget:self action:#selector(removeSelfFromSuperview) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) removeSelfFromSuperview{
[self removeFromSuperview];
}
You are using the finalScore property in the init method of the GameOverView class, but you are only setting its value after initializing it.
Change your initialization method to
- (id) initWithFrame:(CGRect)frame finalScore:(int)fs{
// use 'fs' instead of 'self.finalScore'
}
It should work.
I wonder how there isn't any problem with the view background color. You are initializing the view and adding it as subview like this:
gov = [[GameOverView alloc] initWithFrame:self.view.bounds];
gov.finalScore = self.score;
[self.view addSubview:gov];
This will give the view background color as black which is default color. So you don't find much difference if you use blur effect.
you need to give the color for the view during the initialization :
gov = [[GameOverView alloc] initWithFrame:self.view.bounds];
[gov setBackgroundColor:[UIColor yourColor]];
[self.view addSubview:gov];
If you are planning to keep the code in initWithFrame, you don't need to worry about setting the background color. If you keep the code in drawRect, then you must set the background color,else it will be black color.
When coming to setting the score label, it doesn't matter whether you put it in drawRect or initWithFrame method. Make sure you use drawRect method only if you really have to draw on the view,so that you can call it later by using setNeedsDisplay
I'm trying to add a subview to a view controller programatically, but it seems like if I add the subview in the viewDidLoad of the view controller whatever I have added as subview in the view itself is not rendered in the view controller.
But if I move the [self.view addSubview:myUIView] in the init method of the view controller, everything is rendered.
Also, if I call the same methods in the viewDidAppear I get all the elements rendered, but it's right after the view controller was displayed, and I can see when the elements are rendered in the view controller.
This would be my view controller:
//interface
#class RLJSignInView;
#protocol RLJSignInViewControllerDelegate;
#interface RLJSignInViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, readwrite) RLJSignInView *signInView;
#property (nonatomic, assign) id<RLJSignInViewControllerDelegate> delegate;
#end
//implementation
#import "RLJSignInViewController.h"
#import "RLJSignInView.h"
#import "RLJSignInViewControllerDelegate.h"
#import "UIColor+RLJUIColorAdditions.h"
#implementation RLJSignInViewController
- (id)init
{
self = [super init];
if (self) {
_signInView = [[RLJSignInView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.signInView];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.signInView);
[self.view setBackgroundColor:[UIColor rgbWithRed:250 green:250 blue:250]];
}
#end
And this would be my view:
//interface
#interface RLJSignInView : UIView
#property (strong, nonatomic, readwrite) UITextField *username;
#property (strong, nonatomic, readwrite) UITextField *password;
#property (strong, nonatomic, readwrite) UIButton *signIn;
#property (strong, nonatomic, readwrite) UIButton *signUp;
#end
//implementation
#import "RLJSignInView.h"
#import "UITextField+RLJUITextFieldAdditions.h"
#import "UIButton+RLJUIButtonAdditions.h"
#implementation RLJSignInView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CGRect usernameInputFrame = CGRectMake(10.0, 40.0, self.bounds.size.width - 20, 40);
CGRect passwordInputFrame = CGRectMake(10.0, 79.0, self.bounds.size.width - 20, 40);
CGRect signInButtonFrame = CGRectMake(10.0, 160, self.bounds.size.width - 20, 40);
CGRect signUpButtonFrame = CGRectMake(10.0, 220, self.bounds.size.width - 20, 40);
UIView *usernameInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];
UIView *passwordInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];
self.username = [[UITextField alloc] initWithFrame:usernameInputFrame
textAlignment:NSTextAlignmentLeft
textColor:[UIColor blackColor]
clearButton:UITextFieldViewModeWhileEditing
leftView:usernameInputLeftView
placeholder:#"Username"
backgroundColor:[UIColor whiteColor]
strokeWidth:2.0
strokeColor:[UIColor lightGrayColor]
keyboardType:UIKeyboardTypeEmailAddress
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(4.0, 4.0)
secure:NO];
self.password = [[UITextField alloc] initWithFrame:passwordInputFrame
textAlignment:NSTextAlignmentLeft
textColor:[UIColor blackColor]
clearButton:UITextFieldViewModeWhileEditing
leftView:passwordInputLeftView
placeholder:#"Password"
backgroundColor:[UIColor whiteColor]
strokeWidth:2.0
strokeColor:[UIColor lightGrayColor]
keyboardType:UIKeyboardTypeDefault
byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
cornerRadii:CGSizeMake(4.0, 4.0)
secure:YES];
self.signIn = [[UIButton alloc] initWithFrame:signInButtonFrame
title:#"Sign In"
colorNormal:[UIColor whiteColor]
colorHighlighted:[UIColor whiteColor]
colorDisabled:[UIColor whiteColor]
backgroundNormal:[UIColor colorWithRed:82 / 255.0 green:156 / 255.0 blue:201 / 255.0 alpha:1.0]
cornerRadius:4.0];
self.signUp = [[UIButton alloc] initWithFrame:signUpButtonFrame
title:#"Sign Up"
colorNormal:[UIColor blackColor]
colorHighlighted:[UIColor blackColor]
colorDisabled:[UIColor blackColor]
backgroundNormal:[UIColor whiteColor]
cornerRadius:4.0];
[self addSubview:self.username];
[self addSubview:self.password];
[self addSubview:self.signIn];
[self addSubview:self.signUp];
}
return self;
}
#end
I'm not sure what I could do about it, but I know for sure that I would not like the view to be added to the subview on initialisation.
Or maybe I'm doing the rendering of the subviews of the view wrong. I would appreciate some input on this matter if anyone encountered the same thing or if noticed that I messed up something.
Don't call self.view from inside your init method. This will trigger viewDidLoad being called before your init method even returns, which is incorrect behavior.
Instead, follow this pattern:
create your objects (properties / instance variables) in init, or by using lazy instantiation
add the subviews in viewDidLoad
set the frames in viewWillLayoutSubviews
This pattern will avoid bugs like the one you posted here, and set you up for success handling rotation and resizing events in the future.
The key might be you are using self.view.bounds too early. It might work when you provide an initial size frame (like IB would do).
init is too early. The view controller is initializing, the view is not loaded yet, which means there's no view and the bounds will return CGRectZero.
viewDidLoad is also too early to rely on the view's bounds. The view is just loaded, it does not have a superview yet and might be resized later.
viewWillAppear is the first moment you can rely on the view's bounds. Since it will be resized according to the autoresizingmasks or autolayout constraints.
You can add the subview in viewDidLoad but you you should provide an initial frame (and you set the correct autoresizingmasks or constraints) it should all work fine.
I'm wondering how one would go about programming the kind of dropdown tableview that the Vine app uses. If you have never used Vine, I've provided a picture below which depicts the UI Design I'm talking about. Essentially, when you press the left hand UIBarButton, this tableview drops down. When you touch anywhere again, it drops down a little further (5 or 10 pixels) and then leaves the screen with a nice animation.
Just looking for some feedback on how I might go about implementing this. Thanks in advance.
Not sure a UITableView is the way to go about it.
Perhaps you can use REMenu available on Github to get inspired or fork it to customize to your needs.
The REMenu is as close to an exact copy as you can get. I did notice though that it wasn't clipping the top of the menu when it slid down, it slid underneath the status / nav bar which to me didn't look right. Without having looked at the sliding logic (and with my impressive SE reputation of "8"), this is my quick take on how you make the menu appear.
create a view for the contents of the menu (the table view etc)
put it in an enclosing menu collapsed to a zero height, with the content sticking off the top of the collapsed menu view
set the menu view to clip the contents so the top of the menu is not visible, then animate the contents down, as you animate the menu height larger.
This sample uses a simple gradient for the contents of the menu.
#interface BackgroundLayer : NSObject
+(CAGradientLayer*) redBlueGradient;
#end
#implementation BackgroundLayer
+ (CAGradientLayer*) redBlueGradient
{
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors =
#[(id) [UIColor redColor].CGColor, (id) [UIColor blueColor].CGColor];
headerLayer.locations = nil;
return headerLayer;
}
#end
#interface ViewController ()
#property (nonatomic, strong) UIButton* doIt;
#property (nonatomic, strong) UIView* menu;
#property (nonatomic, strong) UIView* nestedView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create simple toggle button to test the menu
self.doIt = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.doIt.frame = CGRectMake(50, 50, 50, 44);
[self.doIt setTitle:#"Doit!" forState:UIControlStateNormal];
[self.doIt sizeToFit];
[self.doIt addTarget:self action:#selector(doIt:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.doIt];
// menu
self.menu = [[UIView alloc] initWithFrame:CGRectMake(20, 200, 280, 0)];
self.menu.layer.borderColor = [UIColor blackColor].CGColor;
self.menu.layer.borderWidth = 3.0;
self.menu.clipsToBounds = YES;
// menu contents
self.nestedView = [[UIView alloc] initWithFrame:CGRectMake(0, -100, 280, 100)];
CAGradientLayer *background = [BackgroundLayer redBlueGradient];
background.frame = self.nestedView.bounds;
[self.nestedView.layer addSublayer:background];
[self.nestedView clipsToBounds];
[self.menu addSubview:self.nestedView];
[self.view addSubview:self.menu];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction) doIt:(id) sender
{
if (!CGRectEqualToRect(self.nestedView.frame, CGRectMake(0, 0, 280, 100)))
{
[UIView animateWithDuration:0.15 animations:^{
self.menu.frame = CGRectMake(20, 200, 280, 100);
self.nestedView.frame = CGRectMake(0, 0, 280, 100);
}];
}
else
{
[UIView animateWithDuration:0.15 animations:^{
self.menu.frame = CGRectMake(20, 200, 280, 0);
self.nestedView.frame = CGRectMake(0, -100, 280, 100);
}];
}
}
#end
Cheers.
The Problem with the REMenu is, it creates the viewControllers every time the user taps on particular section, which should not be the case. It should persist the state of each screen attached there.
Does anybody know of any controls that will replicate the iOS7 style blur views.
I'm assumming there can be some kind of UIView subclass that will replicate the behavior.
I'm talking about these type views which blur the background extremely thickly so that they have pull effects from the background view.
You might be able to modify something like Bin Zhang's RWBlurPopover to do this. That component uses my GPUImage to apply a Gaussian blur to components underneath it, but you could just as easily use a CIGaussianBlur for the same. GPUImage might be a hair faster though.
That component relies on you being able to capture the view behind the one you're presenting, though, and may have trouble with views that animate behind this content. The need to take a trip through Core Graphics to rasterize the background view will slow things down, so we probably don't have sufficiently direct access to be able to do this in a performant manner for overlays on animating views.
As an update to the above, I recently reworked the blurs in GPUImage to support variable radii, allowing for the complete replication of the blur size in iOS 7's control center view. From that, I created the GPUImageiOS7BlurFilter class that encapsulates the proper blur size and color correction that Apple appears to be using here. This is how GPUImage's blur (on the right) compares to the built-in blur (on the left):
I use a 4X downsampling / upsampling to reduce the number of pixels the Gaussian blur has to operate over, so an iPhone 4S can blur the entire screen in roughly 30 ms using this operation.
You still have the challenge of how to pull content into this blur from views behind this one in a performant manner.
I am using FXBlurView which works great on iOS5+
https://github.com/nicklockwood/FXBlurView
CocoaPods:
-> FXBlurView (1.3.1)
UIView subclass that replicates the iOS 7 realtime background blur effect, but works on iOS 5 and above.
pod 'FXBlurView', '~> 1.3.1'
- Homepage: http://github.com/nicklockwood/FXBlurView
- Source: https://github.com/nicklockwood/FXBlurView.git
- Versions: 1.3.1, 1.3, 1.2, 1.1, 1.0 [master repo]
I added it by using:
FXBlurView *blurView = [[FXBlurView alloc] initWithFrame:CGRectMake(50, 50, 150, 150)];
[self.blurView setDynamic:YES];
[self.view addSubview:self.blurView];
WARNING: someone in the comments stated that Apple rejects apps using this technique. That did NOT happen to me, but just for your consideration.
This may surprise you, but you can use a UIToolbar, which already includes that standard effect (only iOS 7+). In you view controller's viewDidLoad:
self.view.opaque = NO;
self.view.backgroundColor = [UIColor clearColor]; // Be sure in fact that EVERY background in your view's hierarchy is totally or at least partially transparent for a kind effect!
UIToolbar *fakeToolbar = [[UIToolbar alloc] initWithFrame:self.view.bounds];
fakeToolbar.autoresizingMask = self.view.autoresizingMask;
// fakeToolbar.barTintColor = [UIColor white]; // Customize base color to a non-standard one if you wish
[self.view insertSubview:fakeToolbar atIndex:0]; // Place it below everything
Since iOS8 you can use UIBlurEffect.
There are a good exemples on iOS8Sampler with UIBlurEffect and UIVibrancyEffect.
The best new Way to get a blured Overlay is to use the new iOS 8 Feature UIVisualEffectView.
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *bluredView = [[UIVisualEffectView alloc] initWithEffect:effect];
bluredView.frame = self.view.bounds;
[self.view addSubview:bluredView];
The UIBlurEffect supports three kinds of Style. Dark, Light and ExtraLight.
You can create a class with a UIToolBar that is a subclass of UIView and instantiate it in a separate view controller. This approach demonstrates a translucent UIToolBar (subclassed by UIView) that provides live feedback (in this case for an AVCaptureSession).
YourUIView.h
#import <UIKit/UIKit.h>
#interface YourUIView : UIView
#property (nonatomic, strong) UIColor *blurTintColor;
#property (nonatomic, strong) UIToolbar *toolbar;
#end
YourUIView.m
#import "YourUIView.h"
#implementation YourUIView
- (instancetype)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
// If we don't clip to bounds the toolbar draws a thin shadow on top
[self setClipsToBounds:YES];
if (![self toolbar]) {
[self setToolbar:[[UIToolbar alloc] initWithFrame:[self bounds]]];
[self.toolbar setTranslatesAutoresizingMaskIntoConstraints:NO];
[self insertSubview:[self toolbar] atIndex:0];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_toolbar]|"
options:0
metrics:0
views:NSDictionaryOfVariableBindings(_toolbar)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_toolbar]|"
options:0
metrics:0
views:NSDictionaryOfVariableBindings(_toolbar)]];
}
}
- (void) setBlurTintColor:(UIColor *)blurTintColor {
[self.toolbar setBarTintColor:blurTintColor];
}
#end
Once the above UIView has been customized, go ahead and create a class that is a subclass of a ViewController. Below I have created a class that is using an AVCapture session. You must use AVCaptureSession in order to override apple's built in camera configuration. Thus you can overlay the tranclucent UIToolBar from the YourUIView class.
YourViewController.h
#import <UIKit/UIKit.h>
#interface YourViewController : UIViewController
#property (strong, nonatomic) UIView *frameForCapture;
#end
YourViewController.m
#import "YourViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "TestView.h"
#interface YourViewController ()
#property (strong, nonatomic) UIButton *displayToolBar;
#end
#implementation YourViewController
AVCaptureStillImageOutput *stillImageOutput;
AVCaptureSession *session;
- (void) viewWillAppear:(BOOL)animated
{
session = [[AVCaptureSession alloc] init];
[session setSessionPreset:AVCaptureSessionPresetPhoto];
AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:&error];
if ([session canAddInput:deviceInput]) {
[session addInput:deviceInput];
}
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
CALayer *rootLayer = [[self view] layer];
[rootLayer setMasksToBounds:YES];
CGRect frame = [[UIScreen mainScreen] bounds];
self.frameForCapture.frame = frame;
[previewLayer setFrame:frame];
[rootLayer insertSublayer:previewLayer atIndex:0];
stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG, AVVideoCodecKey, nil];
[stillImageOutput setOutputSettings:outputSettings];
[session addOutput:stillImageOutput];
[session startRunning];
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
/* Open button */
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 350, self.view.bounds.size.width, 50)];
[button addTarget:self action:#selector(showYourUIView:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"Open" forState:UIControlStateNormal];
[button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
button.backgroundColor = [UIColor greenColor];
[self.view addSubview:button];
UIButton *anotherButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, 50)];
[anotherButton addTarget:self action:#selector(showYourUIView:) forControlEvents:UIControlEventTouchUpInside];
[anotherButton setTitle:#"Open" forState:UIControlStateNormal];
[anotherButton setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
anotherButton.backgroundColor = [UIColor redColor];
[self.view addSubview:anotherButton];
}
- (void) showYourUIView:(id) sender
{
TestView *blurView = [TestView new];
[blurView setFrame:self.view.bounds];
[self.view addSubview:blurView];
}
#end
I have a viewController which is obviously a subclass of UIViewController called MapViewController. In this viewController I use GPS to get the location of the user.
I also have a view called DrawCircle. This view is a subclass of UIView.
Using drawCircle I would like to be able to at any time draw on my MapViewController. But I am not sure I am understanding the concept of doing so. I know my drawing code is working, I have used it before. But I don't know how to draw onto MapViewController using DrawCircle.
From what it seems to my whenever I call [myCustomView setNeedsDisplay], it is not calling the DrawRect method in my view.
Here is some code:
MapViewController.h
#import "DrawCircle.h"
#interface MapViewController: UIViewController <CLLocationManagerDelegate>{
DrawCircle *circleView;
}
#property (nonatomic, retain) DrawCircle *circleView;
#end
MapViewController.m
#import "DrawCircle.h"
#interface MapViewController ()
#end
#implementation MapViewController
#synthesize circleView;
- (void) viewDidLoad
{
circleView = [[DrawCircle alloc] init];
[self setNeedsDisplay];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
DrawCircle.m
#import "DrawCircle.h"
#interface DrawCircle()
#end
#implementation DrawCircle
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGPoint point = CGPointMake(40, 137);
CGContextAddEllipseInRect(ctx, CGRectMake(point.x, point.y, 10, 10));
}
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor redColor] CGColor]));
CGContextFillPath(ctx);
}
Also if this offers any help into my thought process, here is my StoryBoard scene.
Where the viewcontrollers custom class is MapViewController and the views custom class is DrawCircle.
**EDIT:**I would also like to mention that, in my DrawCircle.m, I have methods that I am calling from MapViewController.m and are working.
Also. Initially, the DrawRect method is being called but I am not able to manually call using setNeedsUpdate. When debugging, it is not even entering the DrawRect method.
You're creating your DrawCircle, but you're never adding it to the view, e.g.
[self.view addSubview:circleView];
Therefore, it's falling out of scope and (if using ARC) getting released on you. You also don't appear to be setting its frame, such as:
circleView = [[DrawCircle alloc] initWithFrame:self.view.bounds];
Alternatively, you could add the view in interface builder (a standard UIView, but then specify your custom class right in IB).
Also, note, you generally don't even to call setNeedsDisplay. The adding it to the view hierarchy will call this for you. You only need to call this if you need to update the view based upon some custom property.
Personally, I'd be inclined to define drawRect like so:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
// I'd just use `rect` and I can then use the `frame` to coordinate location and size of the circle
CGContextAddEllipseInRect(ctx, rect);
// perhaps slightly simpler way of setting the color
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
CGContextFillPath(ctx);
}
This way, the location and size of the circle will be dictated by the frame I set for the circle (by the way, I apologize if this makes it more confusing, but I use a different class name, CircleView, as I like to have View in the name of my UIView subclasses).
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *circle;
circle = [[CircleView alloc] initWithFrame:CGRectMake(100.0, 100.0, 200.0, 200.0)];
circle.backgroundColor = [UIColor clearColor]; // because it calls `super drawRect` I can now enjoy standard UIView features like this
[self.view addSubview:circle];
circle = [[CircleView alloc] initWithFrame:CGRectMake(300.0, 300.0, 10.0, 10.0)];
circle.backgroundColor = [UIColor blackColor]; // because it calls `super drawRect` I can now enjoy standard UIView features like this
[self.view addSubview:circle];
}