Is this the way to create a custom UIView? - ios

I've subclassed UIView so I can add multiple view of the same type to my project. The view is basically a rectangle with a radius, a picture in the left side and a piece of text on the right. Now don't get me wrong the code bellow works and all but i'm always asking myself "is this the way, or am I getting it really wrong".
1) I'm mixing CALayers with UILabels, is this ok?
2) Is the setUpView the way its done.
3) If this is ok, whats my best bet for letting the imageLayer respond to touch events?
4) Could I just ask a side question as well. As a part time beginner with only one basic app in the store am I been a bit picky. What I mean is, should I just make it work and crack on! What do you think?
#synthesize dateLabel = _dateLabel;
#synthesize nameLabel = _nameLabel;
#synthesize photo = _photo;
#synthesize roundRect= _roundRect;
#synthesize imageLayer = _imageLayer;
-(void)setUpView
{
//create the white rectangle
self.roundRect.frame = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height);
self.roundRect.cornerRadius = 15.0;
self.roundRect.backgroundColor = [UIColor clearColor].CGColor;
self.roundRect.borderColor = [UIColor whiteColor].CGColor;
self.roundRect.borderWidth = 2.0;
self.roundRect.masksToBounds = YES;
//create the photo
CGImageRef image = [self.photo CGImage];
self.imageLayer.frame = CGRectMake(0.0, 0.0, self.roundRect.frame.size.width*0.48, self.roundRect.frame.size.height);
self.imageLayer.borderColor = [UIColor whiteColor].CGColor;
self.imageLayer.borderWidth = 2.0;
self.imageLayer.masksToBounds = YES;
[self.imageLayer setContents:(__bridge id)image];
[self.imageLayer setContentsGravity:kCAGravityResizeAspectFill];
[self.imageLayer setContentsRect:CGRectMake(0.0,0.0,1.1,1.0)];
//create label
self.nameLabel.frame = CGRectMake(self.imageLayer.frame.size.width+5, self.roundRect.frame.size.height*0.05, self.roundRect.frame.size.width*0.48,self.roundRect.frame.size.height*0.35);
self.nameLabel.backgroundColor = [UIColor clearColor];
self.nameLabel.textAlignment = UITextAlignmentCenter;
self.nameLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
self.nameLabel.textColor = [UIColor whiteColor];
self.nameLabel.adjustsFontSizeToFitWidth =YES;
self.nameLabel.font = [UIFont fontWithName:#"Gill Sans" size:50.0];
[self.roundRect addSublayer:self.imageLayer];
[self.layer addSublayer:self.roundRect];
[self addSubview:self.nameLabel];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)setPhoto:(UIImage *)photo
{
_photo = photo;
[self setUpView];
}
-(void)setNameLabel:(UILabel *)nameLabel
{
_nameLabel = nameLabel;
[self setUpView];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.roundRect = [[CALayer alloc]init];
self.imageLayer = [[CALayer alloc]init];
self.nameLabel = [[UILabel alloc]init];
[self setUpView];
}
return self;
}

You could make this easier by using the Interface Builder. I would create a XIB with a UIView set to the size I want. Then add a label (nameLabel) and configure it how you like. You can use QuartzCore layers cornerRadius to set your rounded corners. Then you can use your subclass in the XIB and set it as the class type for the UIView. Not the file owner but the view. Then you can link your label as an outlet and you won't need to create it manually. You can remove some of the code above and still have your functionality and looks. I've learned to let the Interface Builder do the work when you can.
Hope this helps.

Related

In Objective-C, how can I change the property of an instance inside of class via UIControl?

This question is about the exercise 6.9 of iOS Programming 4th Edition.
I have a class that can draw concentric circles and respond to touches by changing its color randomly.
Here is the implementation of the view:
#implementation LTHypnosisView
- (instancetype) initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self){
self.backgroundColor = [UIColor clearColor];
self.circleColor = [UIColor lightGrayColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
//Draw the circles
}
- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
float red = (arc4random() % 100) /100.0;
float green = (arc4random() % 100) /100.0;
float blue = (arc4random() % 100) /100.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
self.circleColor = randomColor;
}
- (void) setCircleColor:(UIColor *)circleColor{
_circleColor = circleColor;
[self setNeedsDisplay];
}
#end
Here is the implementation of the viewController:
#implementation LTHypnosisViewController
- (void) viewDidLoad{
CGRect screenRect = self.view.bounds;
LTHypnosisView *mainView = [[LTHypnosisView alloc] initWithFrame:screenRect];
[self addSubview:mainView]
NSArray *items = #[#"Red",#"Green",#"Blue"];
UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems:items];
[segControl addTarget:self
action:#selector(change:)
forControlEvents:UIControlEventValueChanged];
segControl.frame = CGRectMake(10, 50, self.view.bounds.size.width-20, 60);
segControl.selectedSegmentIndex = 0;
segControl.backgroundColor = [UIColor whiteColor];
[self.view addSubview:segControl];
}
- (void) change:(UISegmentedControl*)sender{
switch (sender.selectedSegmentIndex) {
//How should I write here??
}
}
#end
I want to change the color of the concentric circles with the UISegmentedControl. Consequently I need a method within the viewController to change the _circleColor property of the LTHypnosisView instance. How can I do that?
A simple way, I'd make LTHypnosisView *mainView a property of the view controller, make setCircleColor public, and then in change: method call [self.mainView setCircleColor:myColor];
You could also do something more involved, like define a protocol on LTHypnosisView and set the view controller as the datasource. Whenever the change: method is called, call something like [mainView updateColor] and within updateColor have it read the color from it's datasource.

Use of UIView to abstract away common UI (much like a "partial" in web) drawRect vs init vs layoutSubviews

I'm trying to make my View Controllers leaner by abstracting common UI away into UIView subclasses and just instantiating them in my VCs, much like I would create HTML templates in web dev.
Here is my VC:
- (void)addEmptyView {
self.emptyHomeView = [[PLOTEmptyHomeView alloc] initWithFrame:CGRectMake(0, 10, self.view.bounds.size.width, self.view.bounds.size.height - 113) andUser:self.userModel.user];
[self.view addSubview:self.emptyHomeView];
}
And the UIView I subclass and instantiate:
- (id)initWithFrame:(CGRect)frame andUser:(NSDictionary *)user {
self = [super initWithFrame:frame];
if(self){
self.user = user;
[self drawUI];
}
return self;
}
- (void)drawUI {
self.arrowUp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrow-up"]];
CGRect arrowUpFrame = self.arrowUp.frame;
arrowUpFrame.origin.x = self.bounds.size.width - 60;
arrowUpFrame.size.width = 33.75;
arrowUpFrame.size.height = 33;
self.arrowUp.frame = arrowUpFrame;
[self addSubview:self.arrowUp];
self.welcome = [[UILabel alloc] initWithFrame:CGRectMake(0, 167.5, self.bounds.size.width, 22.5)];
NSArray *nameParts = [self.user[#"name"] componentsSeparatedByString:#" "];
self.welcome.text = [NSString stringWithFormat:#"Hey %#", nameParts[0]];
self.welcome.font = [UIFont fontWithName:#"Avenir-Light" size:24];
self.welcome.textColor = [UIColor plotPlaceholderGrey];
self.welcome.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.welcome];
}
Now this code all works fine, but for my own understanding, am I abusing the init method? I'm also aware of layoutSubviews & drawRect in UIView but i'm not sure if I should be using them in the above scenario?
Any pointers are appreciated...
Your not abusing of init method, but you have to declare all your ui objects and all their properties without define the frame and in the layoutsubviews, you define the frame of all your object. It's my way to declare an custom view.
- (id)initWithFrame:(CGRect)frame andUser:(NSDictionary *)user {
self = [super initWithFrame:frame];
if(self){
self.user = user;
self.arrowUp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrow-up"]];
[self addSubview:self.arrowUp];
self.welcome = [[UILabel alloc] init];
NSArray *nameParts = [self.user[#"name"] componentsSeparatedByString:#" "];
self.welcome.text = [NSString stringWithFormat:#"Hey %#", nameParts[0]];
self.welcome.font = [UIFont fontWithName:#"Avenir-Light" size:24];
self.welcome.textColor = [UIColor plotPlaceholderGrey];
self.welcome.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.welcome];
}
return self;
}
- (void)layoutSubviews {
CGRect arrowUpFrame = self.arrowUp.frame;
arrowUpFrame.origin.x = self.bounds.size.width - 60;
arrowUpFrame.size.width = 33.75;
arrowUpFrame.size.height = 33;
self.arrowUp.frame = arrowUpFrame;
self.welcome.frame =CGRectMake(0, 167.5, self.bounds.size.width, 22.5)];
}
Hope it will help you.
Optimally, you can instantiate and add subviews in your method as shown, but do actual layout involving frames in layoutSubviews. The code you have will work as is, but if your initialize would be from a nib or something that doesn't provide the core frame (or autolayout was involved) you may not truly know the proper bounds/frame at init time. Often layout code is dependent on other factors that may change so layoutSubviews is a more proper location.

Views disappearing from UITableViewCell when selected

I have a UITableView with custom UITableViewCells. The content of the cell is kept in a separate class extending UIView (i have 3 kind of contents which i switch by hiding and showing views, i took this approach because of the way i wanted to make the transition between cells).
So let's assume i have one of the views visible and added to the contentView of the cell. This view contains some text and some rectangles made of UIViews. Everything good till now, but the weird thing is when i touch the cell, the rectangles simply disappears, the text stays the same. Do you know what might be the problem? I tried to add the view to the backgroundView as well, it's doing the same.
- (id) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.active = NO;
state = -1;
// Touch is not working if the view has no bounds
self.backgroundView = [[UIView alloc] init];
self.backgroundColor = [UIColor clearColor];
self.selectedBackgroundView = [[UIView alloc] init];
self.selectedBackgroundView.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
self.contentView.clipsToBounds = YES;
// Add empty view
emptyView = [[View1 alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) andRadius:CELL_DOT_RADIUS];
emptyView.userInteractionEnabled = NO;
[self.backgroundView addSubview:emptyView];
The view:
- (id) initWithFrame:(CGRect)frame andRadius:(int)r {
self = [super initWithFrame:frame]; radius = r;
if (self) {
int outerlineWeight = 1;
timelineView = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width/4, frame.size.height/2, 1, 1)];
[self addSubview:timelineView];
dotView = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width/4-radius, frame.size.height/2-radius, radius*2, radius*2)];
dotView.layer.cornerRadius = radius;
dotView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:dotView];
UIView *n = [[UIView alloc] initWithFrame:CGRectMake(160, 0, 50, 20)];
n.backgroundColor = [UIColor redColor];
[self addSubview:n];
middleText = [[UILabel alloc] initWithFrame:dotView.frame];
middleText.font = [UIFont systemFontOfSize:12];
middleText.textColor = [UIColor grayColor];
middleText.backgroundColor = [UIColor clearColor];
middleText.textAlignment = NSTextAlignmentCenter;
[self addSubview:middleText];
This are the sources so you can try for yourself if you want. As you can see the orange rectangle disappears but the text not. For what i need to do nothing should disappear, in the best case i want to change their colours. http://ge.tt/6wRBObD1/v/0?c
You are adding it to background view. When selected the selected background view will display not background view. In your custom cell try
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
if (selected) {
[self.backgroundView addSubview:emptyView];
}
else{
[self.selectedBackgroundView addSubview:emptyView];
}
// Configure the view for the selected state
}
Or add empty view to contentview
one thing for frame settings u need to override "layoutsubviews" method ...
in custom cell
init all the views in initialisation method i,e ur - (id) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier method and add it to cell
in - (void)layoutSubviews just set the frame for ur views in the cell,
use tag property to access the views in the layoutsubviews, if it is not the property
- (id) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.active = NO;
state = -1;
// Touch is not working if the view has no bounds
self.backgroundView = [[UIView alloc] init];
self.backgroundColor = [UIColor clearColor];
self.selectedBackgroundView = [[UIView alloc] init];
self.selectedBackgroundView.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
self.contentView.clipsToBounds = YES;
// Add empty view
//emptyView = [[View1 alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) andRadius:CELL_DOT_RADIUS];//dont set the frame heare do it layoutsubviews
emptyView = [[View1 alloc] init];
emptyView.userInteractionEnabled = NO;
emptyView.tag = 100;
[self.backgroundView addSubview:emptyView];
- (void)layoutSubviews
{
[super layoutSubviews];
UIView *emptyView = [self viewWithTag:100];//your background view heare u can get the frame values for ur cell
emptyView.frame = self.bounds;//better do it for other views in cell also
//test it by setting some background color
}
Hope this helps u ...:)
Ok, so like i've said in my last comment the backgroundColor of any subview is the problem, in the selected state is cleared. For cells created in interface builder the problem does not occurs, my cells are created programatically.
I fixed this by drawing with CoreGraphics in drawRect method.
As with a UICollectionView a UITableView has a background view that is build before AutoLayout. If you use autoLayout you should make sure the backgroundView has a frame. Otherwise is will disappear.
In your cell subclass you can do:
-(void)setBackgroundView:(UIView *)backgroundView
{
super.backgroundView = backgroundView;
self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
[self.backgroundView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero];
}
In swift you need to declare viewcontroller object globally that would result in Strong, in case if you declare locally it results in keep disappearing the cells.
var refineViewController : RefineViewController?
then you can access that controller using below code that would not result in disappearing cells.
func showRefineView(isFindHomeTab isFindHomeTab : Bool){
refineViewController = RefineViewController(nibName: String(BaseGroupedTableVC),bundle : nil)
refineViewController!.refineSearchDelegate = self
refineViewController!.view.frame = CGRectMake(0, -490, self.view.frame.size.width, self.view.frame.size.height)
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseOut, animations:
{
self.refineViewController!.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)
self.refineViewController!.isFindHomeTab = isFindHomeTab
}, completion: nil)
self.view.addSubview(refineViewController!.view)
}

Custom UIPopoverBackgroundView: no drop shadow

I've used this good tutorial to create a custom UIPopoverBackgroundView class.
It works well. The only problem is that I am not getting the typical UIPopoverController drop shadow and I want it. I've tried specifying it on the layer of my UIPopoverBackgroundView instance without success. My instance of UIPopoverController doesn't seem to have a public view to manipulate. Adding it to the popover content also doesn't work.
Probably really simple: how do I add a drop shadow when using a custom UIPopoverBackgroundView class?
// UIPopoverBackgroundView.m
-(id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
_borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"bg-popover.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
_arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bg-popover-arrow.png"]];
[self addSubview:_borderImageView];
[self addSubview:_arrowView];
self.layer.shadowOffset = CGSizeMake(50, 50);
self.layer.shadowColor = [[UIColor blackColor] CGColor];
}
return self;
}
You don't need to add your own shadows. The base UIPopoverBackgroundView will do it for you. Just make sure to call super in your layoutSubviews implementation.
EDIT: My comment applies to apps targeting iOS 6.
Ok, figured it out. I needed to add the drop shadow to the borderImageView, not the view of the popover instance.
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
_borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"bg-popover.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
_arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bg-popover-arrow.png"]];
[self addSubview:_borderImageView];
[self addSubview:_arrowView];
_borderImageView.layer.cornerRadius = 5.0f;
_borderImageView.layer.masksToBounds = NO;
_borderImageView.layer.borderWidth = 1.0f;
_borderImageView.layer.borderColor = [UIColor blackColor].CGColor;
_borderImageView.layer.shadowColor = [UIColor blackColor].CGColor;
_borderImageView.layer.shadowOpacity = 0.8;
_borderImageView.layer.shadowRadius = 50;
_borderImageView.layer.shadowOffset = CGSizeMake(-10.0f, 10.0f);
}
return self;
}

Exlusive touch in cameraoverlay view (of the camerapicker) in iOS?

In my application, I added a cameraOverlayView to the camerapicker. It contains three subviews, two UIButtons and one UIImageView. The image is draggable. The buttons are used to change the image to another one.
When I touch a button, or the image, the tap-to-focus rect of the camera will always be displayed beneath (and the actual focus will change). Setting exclusiveTouch to YES on all three subviews doesn't change the behaviour.
Any idea, how i can stop the camerapicker from getting the touches of my buttons / subviews of my overlayview (but still getting the touches for touch-to-focus, switching cameras etc.)?
To give some more information. I'm using the following code, to allow camera interaction while having an overlayview, which subviews still get the touches.
// CameraOverlayView.m
[...]
// ignore touches on this view, but not on the subviews
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
id hitView = [super hitTest:point withEvent:event];
if (hitView == self) return nil;
else return hitView;
}
There are a couple of ways to stop this that I found. None of the ways do exactly what I would like it to do. But tweaking it around should give you something usable.
1.) Disable camera control. cameraPickerController.showsCameraControls = NO; This will ultimately take down the touch to focus from the screen but it would take down other controls too.
2.) Control the hitTest in your overlayView and set those area to nil. This solution is very awkward but it works for me.
This is my overlayView that manages a face mask
- (id)initWithFrame:(CGRect)frame imagePicker: (UIImagePickerController *) imagepicker{
self = [super initWithFrame:frame];
if (self) {
faceMaskButton = [UIButton buttonWithType:UIButtonTypeCustom];
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:.2];
faceMaskButton.frame = CGRectMake(10, 10, 70, 35);
faceMaskButton.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:.6] CGColor];
faceMaskButton.layer.borderWidth = 1;
faceMaskButton.layer.cornerRadius = 17;
[faceMaskButton setImage:[UIImage imageNamed:#"Facemask button"] forState:UIControlStateNormal];
[faceMaskButton addTarget:self action:#selector(facemask) forControlEvents:UIControlEventTouchDown];
faceMaskButton.exclusiveTouch = YES;
faceMaskButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
[self addSubview:faceMaskButton];
loadingFacemask = NO;
overlayImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Camera Face Overlay"]];
overlayImage.frame = CGRectMake((self.bounds.size.width - 400)/2, (self.bounds.size.height - 400)/3, 400, 400);
overlayImage.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
overlayImage.alpha = 0.9f;
[self addSubview:overlayImage];
imagePickerController = imagepicker;
}
return self;
}
- (void) facemask{
if (!loadingFacemask) {
loadingFacemask = YES;
[UIView animateWithDuration:.3 animations:^{
overlayImage.alpha = overlayImage.alpha? 0:1;
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
} completion:^(BOOL finished) {
faceMaskButton.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:.2];
loadingFacemask = NO;
}];
}
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if ((self.bounds.size.height - 44)<point.y){
return imagePickerController.view;
}
if (point.x < faceMaskButton.bounds.origin.x + faceMaskButton.bounds.size.width +10 && point.y < faceMaskButton.bounds.origin.y + faceMaskButton.bounds.size.height + 10) {
[self facemask];
return nil;
}
return self;
}
So if you get tweaking, this will work for you. I am still looking for the magic wand that make this work without all the hacks. No sign of it still :(

Resources