I have to create a custom shaped (inverted T) bordered Uiview on iOS. I am attaching the screenshot below. I have researched a lot and I found a way using UIBezierPath from here.
But I didn't get any idea to shape my view as inverted T shaped.
UIViews are always rectangular. From the documentation:
The UIView class defines a rectangular area on the screen and the interfaces for managing the content in that area.
Even though the view is limited to being rectangular, you can shape your active area in any way that you like. By combining that with a transparent background, you can imitate a view of any shape that you can draw.
When your rectangular view receives touches and other events, your event handlers should first check if the activity has happened in the inverted-T area. If the activity is outside, the event should be ignored.
Phew.. Finally I have done it. I have used two UiViews subclasses (top & bottom).
The main challenge I faced was about the border, because if I set the border to my two views (top & bottom), it will not show as a single container item. :)
Steps that I done:
Created two UiView subclasses. Lets call topView and bottomView.
TopView *topView = [[TopView alloc] initWithFrame:CGRectMake(220, 60, 200, 200)];
[topView setBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:topView];
BottomView *bottomView = [[BottomView alloc] initWithFrame:CGRectMake(130, 260, 380, 200)];
[bottomView setBackgroundColor:[UIColor yellowColor]];
bottomView.customShape = topView; //Set the custom shape as TopView to frame to draw the border.
[self.view addSubview:topView];
I have drawn three borders (top,right,left) for TopView and two full borders (bottom, right), two partial borders (top left, top right) for BottomView through overriding the drawRect method.
See my TopView class here.
See my BottomView class here.
Thanks to all.
Output:
It should be possible to create a view with a CAShapeLayer as layer.
Make a subclass of UIView and override the layerClass method:
+ (Class)layerClass {
return [CAShapeLayer class];
}
Then in the viewDidLoad you can specify the bezierPath to the shapeLayer:
- (void)viewDidLoad {
[(CAShapeLayer *)self.layer setPath:someBezierPath.CGPath];
[self.layer setBackgroundColor:[UIColor redColor].CGColor];
}
Related
I am trying to make a drawing application following this tutorial: http://www.effectiveui.com/blog/2011/12/02/how-to-build-a-simple-painting-app-for-ios/, and then I tried to make it such that I don't only draw on the entire screen, I only draw on a UIView that is inside of another UIView. (What I call a nested UIView)
My code is currently on github: https://github.com/sammy0025/SimplePaint
The only parts I tweaked with the original code from the tutorial is to change some class prefix names, enabled ARC so no deallocs, used storyboards (which works fine with the original code from the tutorial), and change this code in the main view controller implementation file (SPViewController.m):
-(void)viewDidLoad
{
[super viewDidLoad];
nestedView.backgroundColor=[UIColor colorWithWhite:0 alpha:0]; //This is to make the nested view transparent
SPView *paint = [[SPView alloc] initWithFrame:nestedView.bounds]; //original code is SPView *paint=[[SPView alloc] initWithFrame:self.view.bounds];
[nestedView addSubview:paint]; //original code is [self.view addSubview:paint];
}
My question is how do I make sure that I only draw inside the nested UIView?
Add clipping to your sub view. E.g. self.bounds would cover the whole area of a view.
UIBezierPath *p = [UIBezierPath bezierPathWithRect:self.bounds];
[p addClip];
I believe clipping for a view should be based on bounds initially and that you may shrink it by adding an new clipping. But there might be a difference between iOS and OS X here.
Rather than using an SPView inside a nested view, consider just changing the frame of the SPView to match what you want. This should solve your problem of only allowing drawing within a given rect.
I think you're seeing issues because of your storyboard configuration. I dont know much about storyboard, but I was able to get this to work programmatically by throwing out storyboard's view and building my own in viewDidAppear.
-(void)viewDidAppear:(BOOL)animated{
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
newView.backgroundColor = [UIColor greenColor];
SPView *newPaint = [[SPView alloc] initWithFrame:CGRectInset(self.view.bounds, 40,40)];
[newView addSubview:newPaint];
self.view = newView;
}
According to the official doc on UIView about the contentMode property:
The content mode specifies how the cached bitmap of the view’s layer is adjusted when the view’s bounds change
What's defined the content in this definition? Is it a sub view or when we have define a background color for a view for example.
My very first guess was that it should apply at least for the subviews in a view, but for example the following code snippet will not give me the expected result when playing with the UIViewContentModeCenter tag:
UIView* redView = [[UIView alloc] initWithFrame:CGRectMake(80, 80, 150, 200)];
redView.contentMode = UIViewContentModeCenter;
redView.backgroundColor = [UIColor redColor];
UIView* greenView = [[UIView alloc] initWithFrame:redView.bounds];
greenView.backgroundColor = [UIColor greenColor];
[redView addSubview:greenView];
redView.frame = CGRectInset(redView.frame, -5, -5);
[self.view addSubview:redView];
I have just set up a redView that will include a greenView. I have also set-up the content mode of the redview to UIViewContentModeCenter - why in the code I wrote the greenView is not centered when I change the frame of its parent? isn't what UIViewContentModeCenter is supposed to do?
Thanks for clarifying!
Ps: You can easily test the above code in the loadView of a simple view controller template project.
From the documentation:
The content mode specifies how the cached bitmap of the view’s layer
is adjusted when the view’s bounds change.
For an image view, this is talking about the image. For a view that
draws its content, this is talking about the drawn content. It does
not affect the layout of subviews.
You need to look at the autoresizing masks in place on the subviews.
Content mode is a red herring here. If you can't achieve the layout
you need using autoresizing masks, then you need to implement
layoutSubviews and calculate the subview positions and frames
manually.
from jrturton's answer to: https://stackoverflow.com/a/14111480/1374512
First read about Content Modes here
In your example you change the frame of the red view. That will invoke layoutSubviews on the view which will reposition the green view according to the layout constraints or autoresizing masks. You haven't specified any. So the frame of the green view will stay the same.
The content mode specifies how the view's layer should update when resizing. Depending on the content mode drawRect will be called or not.
You can test the effect of the different content modes with the following example:
Add a UIView subclass, that draws a circle using this drawRect implementation:
- (void)drawRect:(CGRect)rect
{
// Drawing code
NSLog(#"drawRect %#", NSStringFromCGRect(rect));
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, self.bounds);
[[UIColor redColor] setFill];
CGContextFillPath(ctx);
}
In the view controller create and add the circle view:
CircleView* circleView = [[CircleView alloc] initWithFrame:CGRectMake(10, 10, 20, 20)];
circleView.contentMode = UIViewContentModeCenter; // <- try different modes here
[self.view addSubview:circleView];
Now lets animate the frame and see what happens:
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:5 animations:^{
circleView.frame = CGRectMake(10, 10, 100, 200);
}];
});
I'm doing that asynchronously to force CoreGraphics to draw the view at least one time with the original frame.
When you don't set the content mode you end up with a blurry circle, because it just scales up without redrawing. UIViewContentModeCenter makes the small circle stay at the center - also no redraw needed. UIViewContentModeRedraw makes the view draw the view again with the new frame. Actually that happens before the animation starts.
Note that the content mode can affect the drawing performance.
I know if I want to add a black background to a UIImageView, I can create an UIImageView that is a bit smaller than the UIView it's contained in. If I do this, the UIImageView is positioned on top of the black background.
Just hypothetically speaking, suppose I want the black background UIView on TOP of the UIImageView (so that it covers the image), how do I do this? Is there a concept of a z-index?
Here's my code to add a black background to an image:
UIView *blackBG = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
blackBG.backgroundColor = [UIColor blackColor];
UIImageView *myPicture = [[UIImageView alloc] initWithImage:
[UIImage imageNamed: #"myPicture.jpg"]];
int borderWidth = 10;
myPicture.frame = CGRectMake(borderWidth,
borderWidth,
blackBG.frame.size.width-borderWidth*2,
blackBG.frame.size.height-borderWidth*2)];
[blackBG addSubview: myPicture];
Subviews are layered, you can send a subview to the back with...
[myView sendSubviewToBack:mySubView];
or bringSubviewToFront etc.
You can also do one of these...
[myView insertSubview:mySubview atIndex:0]; //Places it at the bottom
[myView insertSubview:mySubview belowSubview:anotherSubview]; //Places it below anotherSubview
As soon as you add a view as a subview to another view however, the parent view is the bottom of the stack. You can play around with CALayer z orders but you are bypassing the general approach of views and subviews.
Create a single view as a parent for both the bg view and the imageview, then you can order them how you like.
UPDATE: I have a UIView+Layout category which has nice [subview sendToBack]; and [subview bringToFront]; which you may find useful.... article here
I have a UIView whose color is black and opacity is 0.9 or something like that. What I want to do is to fill the UIView with black portion, but a specified rectangular area should not get black. i.e. that particular rectangular area remains with clear color...
Any kind of help will be appreciated.
regards,
Imran
You can subclass UIView and override - (void)drawRect:(CGRect)rect
And do something of the following (untested, but used something similar myself);
- (void)drawRect:(CGRect)rect {
// fill the whole UIView with a color
[[UIColor colorWithRed:1 green:1 blue:1 0.9] setFill];
UIRectFill( rect );
// draw a rect
CGRect rectIntersection = CGRectIntersection(theRectIWantToDrawIn, rect);
[[UIColor clearColor] setFill];
UIRectFill( rectIntersection );
}
The code above draws a black view with a simulated clearColor hole in it at a certain position. You could, of course, alter this behavior to your liking.
This is quite fast.
UPDATE: Do note that I set the UIView on which I want to draw the rect to UIClear color (I added it to a xib and set those properties there) and unchecked 'Opaque'. That should make the hole see-through.
UPDATE 2: My answer was based on this answer (credit where credit is due): iPhone - Draw transparent rectangle on UIView to reveal view beneath
Add another view (subview) to that area. And set its to your desired color. Thats the easy solution with your current requirements.
Or you can use some quartz core functions.
You can create a small UIView in the main view by custom and set background clear color
UIView *View1 = [[UIView alloc] initWithFrame:CGRectMake(128.0f, 50.0f, 34.0f, 34.0f)];
[View1 setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:View1];
[View1 release];
i have a UIView that is smaller than the superview so i can represent this view as a modal view when a button is clicked.
I have managed to do the following:
* add a subview to the superview.
* centered this modal view
I am now trying to make the elements behind the UIView unclickable. And also add a grey shadow te the ourside of my modal view so that the user understands that the modal view is the view in focus.
I would like to know how to achieve this.
I do not wish to use the presentation modal transition. I know and have already implemented this in other projects.
Any help is appreciated.
The simplest thing would be to lay a fullscreen UIView with a translucent gray background behind your "modal" view. Then it will intercept all of the touches. It might look something like this:
UIView *dimBackgroundView = [[UIView alloc] initWithFrame:theSuperview.bounds];
dimBackgroundView.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:.5f];
[theSuperview addSubview:dimBackgroundView];
[theSuperview addSubview:modalView];
For future reference, you can set myView.userInteractionEnabled = NO to disable touch events on a view.
There are several ways to do it.
If you have a custom view which has custom location, you can modify it like that:
Create an instance var:
UIView* backgroundView;
And whenever you need it, put it behind your custom view:
if (backgroundView == nil)
backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width)];
backgroundView.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:.5f];
[self.view addSubview:backgroundView];
[backgroundView animateBump:customView.view];
[backgroundView addSubview:customView.view];
When you do not need it anymore
[backgroundView removeFromSuperview];