I managed to get a UIView in greyscale by adding the following view on top:
#interface GreyscaleAllView : UIView
#property (nonatomic, retain) UIView *underlyingView;
#end
#implementation GreyscaleAllView
#synthesize underlyingView;
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// draw the image
[self.underlyingView.layer renderInContext:context];
// set the blend mode and draw rectangle on top of image
CGContextSetBlendMode(context, kCGBlendModeColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextFillRect(context, rect);
[super drawRect:rect];
}
#end
It works, but the content doesn't get updated unless i manually call setNeedsDisplay. (I can press a UIButton and the action fires, but nothing changes in appearance) So for it to behave like expected I call setNeedsDisplay 60 times each second. What am I doing wrong?
UPDATE:
The viewcontroller inits the overlayview with this:
- (void)viewDidLoad
{
[super viewDidLoad];
GreyscaleAllView *grey = [[[GreyscaleAllView alloc] initWithFrame:self.view.frame] autorelease];
grey.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
grey.userInteractionEnabled = NO;
[self.view addSubview:grey];
}
I've added this for the underlaying views to redraw:
#implementation GreyscaleAllView
- (void)setup {
self.userInteractionEnabled = FALSE;
self.contentMode = UIViewContentModeRedraw;
[self redrawAfter:1.0 / 60.0 repeat:YES];
}
- (void)redrawAfter:(NSTimeInterval)time repeat:(BOOL)repeat {
if(repeat) {
// NOTE: retains self - should use a proxy when finished
[NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
}
[self setNeedsDisplay];
}
What you really want is for the subview to only be told to redraw when the parent view redraws, in which case you can override -setNeedsDisplay in the parent view and make it call -setNeedsDisplay on the GreyscaleAllView. This requires subclassing UIView for your UIViewController's view property.
i.e. implement loadView in the controller, and set
self.view = [[CustomView alloc] initWithFrame:frame];
where CustomView overrides setNeedsDisplay as described above:
#implementation CustomView
- (void)setNeedsDisplay
{
[grayView setNeedsDisplay]; // grayView is a pointer to your GreyscaleAllView
[super setNeedsDisplay];
}
#end
For completeness, you should also do the same for -setNeedsDisplayInRect:
Related
I subclassed UIView in order to draw a line that changes position every second. I am using NSTimer to call setNeedsDisplay in order to update the view but the results are not correct. It is drawing old and new lines. Here is my LineView.h/m code:
LineView.h:
#interface LineView : UIView
#property(nonatomic, assign) int length;
#end
LineView.m:
#import "LineView.h"
#implementation LineView
- (void)setLength:(int)length {
_length = length;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
UIBezierPath *line = [UIBezierPath bezierPath];
[line moveToPoint:CGPointMake(10, 10)];
[line addLineToPoint:CGPointMake(100, 100+_length)];
line.lineWidth = 10;
[[UIColor blueColor] setStroke];
[line stroke];
}
#end
In my ViewController.m file, in viewDidLoad, I init lineView and start the timer:
- (void)viewDidLoad {
[super viewDidLoad];
_length = 100;
_lineView = [[LineView alloc]initWithFrame:self.view.bounds];
_lineView.length = _length;
[self.view addSubview:_lineView];
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:true];
}
- (void)updateTime:(NSTimer*)timer {
_length += 20;
self.lineView.length = _length;
}
I want to do this all programmatically and not use storyboards or xibs. If I subclass the view in storyboard and create an outlet to it, then the view is updated correctly but I want to avoid touching the storyboard.
_lineView.opaque = NO;
_lineView.backgroundColor = [UIColor blackColor];
try this. I have run your project with this it doing good.
I am trying to change self.navigationController.toolbar position to top instead of a bottom.
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIToolbarDelegate, UIBarPositioningDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
// View did load
- (void)viewDidLoad
{
// Superclass view did load method call
[super viewDidLoad];
// UIToolbarDelegate
self.navigationController.toolbar.delegate = self;
}
- (void)viewDidLayoutSubviews
{
self.navigationController.toolbarHidden = NO;
self.navigationController.toolbar.frame = CGRectMake(self.navigationController.toolbar.frame.origin.x, 64.0f, self.navigationController.toolbar.frame.size.width, self.navigationController.toolbar.frame.size.height);
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:#[#"First", #"Second"]];
segmentedControl.frame = CGRectMake(50.0f, 10.0f, 220.0f, 24.0f);
segmentedControl.selectedSegmentIndex = 1;
[self.navigationController.toolbar addSubview:segmentedControl];
}
- (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar
{
return UIBarPositionTopAttached;
}
#end
The position changes at first, but later gets back to the bottom. Please, help me!
First make sure that toolbar delegate method positionForBar: is get called. If not, then your toolbar delegate is not properly set and you've have to define the UIToolbarDelegate protocol inside your header class.
#interface ExampleViewController : UIViewController <UIToolbarDelegate>
...
#end
Then set the delegate and also change the frames for toolbar. However, for the orientation you might have to reconsider the frames of toolbar.
#implementation ExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
// set the toolbar delegate
self.navigationController.toolbar.delegate = self;
self.navigationController.toolbarHidden = NO;
// create a toolbar child items
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:#[#"First", #"Second"]];
segmentedControl.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.with, 24.0f);
segmentedControl.selectedSegmentIndex = 1;
[self.navigationController.toolbar addSubview:segmentedControl];
// set the frames
CGRect toolbarFrame = self.navigationController.toolbar.frame;
toolbarFrame.origin.y = self.navigationController.frame.size.height;
self.navigationController.toolbar.frame = toolbarFrame;
}
// pragma mark - UIToolbarDelegate
- (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar
{
return UIBarPositionTopAttached;
}
#end
In your viewDidLoad method, add:
self.navigationController.toolbar.delegate = self;
I have recently started learning core graphics to draw on views, but every time I call setNeedsDisplay, the method runs but doesn't trigger drawRect. In my project, I have a view on a view controller. The view contains an if statement in drawRect that checks if a BOOL variable is YES or NO. If YES, it uses the code to draw a red circle. If NO, it draws a blue circle. In the BOOL's setter, it calls setNeedsDisplay. I create an instance of this view class in the ViewController and set the BOOL but the view is not redrawing. Why is the method not getting called? I posted the xcode files and code bits below.
Customview.m:
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
-(void)awakeFromNib{
[self setup];
}
-(void)setup{
_choice1 = YES;
}
-(void)setChoice1:(BOOL)choice1{
_choice1 = choice1;
[self setNeedsDisplay];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
if (_choice1) {
UIBezierPath * redCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor redColor]setFill];
[redCircle fill];
}else if (!_choice1){
UIBezierPath * blueCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor blueColor]setFill];
[blueCircle fill];
}
}
#end
CustomView.h:
#import <UIKit/UIKit.h>
#interface CustomView : UIView
#property (nonatomic) BOOL choice1;
#end
ViewController.m:
#import "ViewController.h"
#import "CustomView.h"
#interface ViewController ()
- (IBAction)Change:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Change:(id)sender {
CustomView * cv = [[CustomView alloc]init];
[cv setChoice1:NO];
}
#end
Full project: https://www.mediafire.com/?2c8e5uay00wci0c
Thanks
You need to create an outlet for your CustomView in your ViewController. Delete the code that creates the new CustomView instance and use _cv to refer to the customView.
#interface ViewController ()
- (IBAction)Change:(id)sender;
#property (weak,nonatomic) IBOutlet CustomView *cv;
#end
Then, in the storyboard, connect ViewController's cv outlet to the CustomView. Also, you'll need to implement the initFromCoder: method on CustomView.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self setup];
}
return self;
}
I've implemented a custom (blurred) background for a modal view controller on iPad. Here's how I do it: in the presented VC's viewWillShow: I take a snapshot image of the presenting VC's view, blur it, and add a UIImage view on top of the presenting VC. This works well.
However, when I rotate the device while the modal is showing, the blurred image gets out of sync with the view controller it represents. The image gets stretched to fill the screen, but the view controller doesn't resize its views the same way. So when the modal is dismissed and the blur goes away, the transition looks bad.
I've tried taking another snapshot in didRotateFromInterfaceOrientation:, and replacing the blurred image, but that didn't work.
Any advice on how to solve this?
UPDATE: a sample project
My code (in the modal view controller):
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self drawBackgroundBlurView];
}
- (void)drawBackgroundBlurView
{
if(_blurModalBackground) {
[_backgroundBlurView removeFromSuperview];
CGRect backgroundFrame = self.presentingViewController.view.bounds;
_backgroundBlurView = [[UIImageView alloc] initWithFrame:backgroundFrame];
_backgroundBlurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_backgroundBlurView setImage:[STSUtils imageByBlurringView:self.presentingViewController.view]];
[self.presentingViewController.view addSubview:_backgroundBlurView];
}
}
Utils code
+ (UIImage *)imageByBlurringView:(UIView *)view
{
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
BOOL isPortrait = (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]));
CGRect frame;
if(isPortrait) {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.height, view.frame.size.width);
}
else {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
UIGraphicsBeginImageContextWithOptions(frame.size, NO, window.screen.scale);
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
// Get the snapshot
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
// Apply blur
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
UIImage *blurredSnapshotImage = [snapshotImage applyBlurWithRadius:8
tintColor:tintColor
saturationDeltaFactor:1.8
maskImage:nil];
UIGraphicsEndImageContext();
return blurredSnapshotImage;
}
I coded a simple example that takes a screenshot from presentingViewController and sets it as background image. Here is the implementaion of my modal view controller. And it works well (both on viewWillAppear and handles rotation properly). Could you please also post STSUtils code?
#import "ScreenshoterViewController.h"
#interface ScreenshoterViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
- (IBAction)buttonBackPressed;
#end
#implementation ScreenshoterViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateImage];
}
- (void)updateImage
{
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.presentingViewController.view.layer renderInContext:context];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.imageView.image = img;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self updateImage];
}
- (IBAction)buttonBackPressed {
[self dismissViewControllerAnimated:YES
completion:NULL];
}
#end
Update
In your utils code just replace this
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
with this
[view drawViewHierarchyInRect:frame afterScreenUpdates:YES];
From apple docs:
afterUpdates A Boolean value that indicates whether the snapshot
should be rendered after recent changes have been incorporated.
Specify the value NO if you want to render a snapshot in the view
hierarchy’s current state, which might not include recent changes.
So you were taking screenshot that did not included latest changes in the view.
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];
}