initWithImage works fine initWithFrame is being called more than once - ios

I am sublassing either an UIImageView or UIView for generating simple color tiles with digits. If I am using UIImageView, I use initWithImage method. If I use UIView, the method initWithFrame is being used.
Either red square image or programmatically generated red view is used for initialization.
The problem can be seen on two screenshots: when initWithImage is being used - everything works fine. If initWithFrame method is being used, I am ending with multiple white views without any information created in even order with normal views. All the screenshots and code attached.
This how it looks when initializing using initWithImage:
- (id)initWithImage:(UIImage *)image {
self = [super initWithImage:image];
if (self) {
//Some non-important, label-related stuff.
[self addSubview:self.numberLabel];
}
return self;
}
And this is how it looks with initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.redView = [self redViewWithFrame:frame];
[self addSubview:self.redView];
//label-related stuff
}
return self;
}
- (UIView *)redViewWithFrame:(CGRect)frame {
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor redColor];
view.alpha = 1;
return view;
}
And the for-loop, calling these initializers from another class (UIScrollView subclass). The label value is being set after the view had been initialized.
- (void)setNumberOfBlocks:(NSInteger)numberOfBlocks {
_blocks = [[NSMutableArray alloc] init];
CGSize contentSize;
contentSize.width = BLOCK_WIDTH * (numberOfBlocks);
contentSize.height = BLOCK_HEIGHT;
self.contentSize = contentSize;
for (int i = 0; i < numberOfBlocks; i++) {
CGFloat totalWidth = BLOCK_WIDTH * i;
CGRect frame = CGRectMake(0, 0, BLOCK_WIDTH, BLOCK_HEIGHT);
frame.origin.x = totalWidth;
BlockView *view = [[BlockView alloc] initWithImage:[UIImage imageNamed:#"block.png"]];
OR!!!
BlockView *view = [[BlockView alloc] initWithFrame:frame];
view.frame = frame;
NSString *number = [NSString stringWithFormat:#"%ld", (long)i + 1];
view.numberLabel.text = number;
[self addSubview:view];
[_blocks addObject:view];
}
}
Googling gave me the impression that this is very common problem, but I haven't found any solution how to beat this. Moreover, I still do not understand, why numbers are in totally right order, the only problem is view position.

The problem is that you're setting the frame of the red view to the superview's frame instead of its bounds. Since the red view is a subview of the BlockView, its frame needs to be relative to its superview, which you get with bounds (its not clear why you even need the red view, as opposed to setting the background color of the block view to red).
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.redView = [self redViewWithFrame:self.bounds];
[self addSubview:self.redView];
//label-related stuff
}
return self;
}

I think you should set the frame origin to (0,0) in redViewWithFrame in ordre to hâve superposed views

Related

UIScrollview does not react to any touch?

I am trying to do a simple scroll, but the views do not move after a touch, I am not sure why, the scrollview should handle the gesture, but something might be missing. Would someone know where?
Here is the code : I create a small horizontal scroll view, with some views inside. The views appear well, I am testing it on a device for the touch :
- (void)viewDidLoad {
[super viewDidLoad];
//horizontal scroll view
HorizScroll *ho = [[HorizScroll alloc] initWithFrame:CGRectMake(0, 0, 500, 100)];
for ( int i=0; i<3; i++){
MyView* mv = [[MyView alloc] init];
[ho addSubview:mv];
}
//ho.zoomScale = 0.3f;
[self.view addSubview:ho];
}
#implementation HorizScroll
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
}
return self;
}
-(void)addSubview:(UIView *)view{
[super addSubview:view];
NSUInteger numSubviews = self.subviews.count;
[view setFrame:CGRectMake(CGRectGetWidth(view.bounds)*(numSubviews),
0,
CGRectGetWidth(view.bounds),
CGRectGetHeight(view.bounds) )];
[self setContentSize:CGSizeMake(CGRectGetWidth(view.bounds)*(numSubviews),
CGRectGetHeight(view.bounds) )];
}
#implementation MyView
-(int)getRandomNumberBetween:(int)from to:(int)pto {
return (int)(from + arc4random() % (pto-from+1));
}
-(instancetype)init{
if ( self = [super init] ){
CGFloat red = [self getRandomNumberBetween:1 to:255];
self.backgroundColor = [UIColor colorWithRed:red/256.0
green:[self getRandomNumberBetween:1 to:255]/256.0
blue:[self getRandomNumberBetween:1 to:255]/256.0
alpha:1.0];
}
return self;
}
-(instancetype)initWithFrame:(CGRect)frame{
if ( self = [super initWithFrame:frame] ){
self.frame = CGRectMake(counterX, 0, 100, 100);
counterX += 50;
}
return self;
}
You need to set contentSize of scrollview to be larger than its frame size. so add this line after [self.view addSubview:ho].
ho.contentSize = CGSizeMake(501.f, 100.f);
or before [self.view addSubview:ho] and comment out the line:
[self setContentSize:CGSizeMake(CGRectGetWidth(view.bounds)*(numSubviews),
CGRectGetHeight(view.bounds))];
which is not necessary since you can set it after all subviews are added.

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)
}

UITapGestureRecognizer EXC_BAD_ACCESS in self-contained UIView subclass

I've used UITapGestureRecognizer tons of times, but in this case I'm getting EXC_BAD_ACCESS when a tap occurs. I think it has to do with the fact that I'm adding it to an Alert-type overlay view. And for some reason the overlay view isn't getting retained even though it's on-screen.
I'm creating the view like this:
HelperView *testHelper = [HelperView helperWithBodyText:#"test text"];
[testHelper presentAtPoint:screenCenter];
The convenience method in HelperView.m looks like this:
+ (id)helperWithBodyText:(NSString*)text
{
return [[self alloc] initWithFrame:CGRectZero bodyText:text];
}
And the rest of the code looks like this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.container = [[AGWindowView alloc] initAndAddToKeyWindow];
self.container.supportedInterfaceOrientations = UIInterfaceOrientationMaskLandscapeRight;
self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
self.overlay.backgroundColor = [UIColor redColor];
self.overlay.alpha = 0.6;
[self.container addSubviewAndFillBounds:self.overlay]; //this fills the screen with a transparent red color, for testing
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissMe:)];
[self.overlay addGestureRecognizer:tap]; //I want to dismiss the whole view if the overlay is tapped
self.content = [[UIView alloc] initWithFrame:CGRectZero];
}
return self;
}
- (id)initWithFrame:(CGRect)frame bodyText:(NSString*)bodyText
{
self = [self initWithFrame:frame];
if (self) {
//TEST frame
self.content.frame = CGRectMake(0, 0, 217, 134);
// Initialization code
UIImage *bgImage = [[UIImage imageNamed:#"helper-bg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(30, 28, 20, 20)];
UIImageView *bgImgView = [[UIImageView alloc] initWithImage:bgImage];
bgImgView.bounds = self.content.frame;
[self.content addSubview:bgImgView];
}
return self;
}
- (void)presentAtPoint:(CGPoint)loc
{
CGPoint newPoint = [self.container convertPoint:loc toView:self.container];
self.content.center = newPoint;
[self.container addSubview:self.content];
}
- (void)dismissMe:(UITapGestureRecognizer*)recognizer
{
//this never happens - I get EXC_BAD_ACCESS when I tap the overlay
}
HelperView is not the view getting displayed and it's not getting retained, but you're using as the target for the gesture recognizer. The AGWindowView property "container" is getting displayed and retained by its superview. Your code needs refactoring since you have this view HelperView that doesn't ever display anything itself, but if you want it to work like this you need to retain HelperView so it doesn't automatically get released. You can do this by assigning it to a strong instance variable.

Show view from xib in ScrollView

firstly I want to say that I am newbie at iOS development. I have a problem with showing a view from xib file in ScrollView.
This MyView is only green background with one label. Here is a code from its controller:
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//init code
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
}
return self;
}
#end
And here is a code from main controller where is the ScrollView:
- (void)viewDidLoad
{
[super viewDidLoad];
scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 67, self.view.frame.size.width, self.view.frame.size.height)];
scroll.pagingEnabled = YES;
scroll.frame = CGRectMake(0, 20, 320, 350);
scroll.contentSize = CGSizeMake(320, 350);
scroll.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scroll.bounces = YES;
scroll.bouncesZoom = YES;
scroll.scrollEnabled = YES;
scroll.minimumZoomScale = 0.5;
scroll.maximumZoomScale = 5.0;
scroll.delegate = self;
scroll.pagingEnabled = YES;
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
UIView *myView = [nibContents objectAtIndex:0];
myView.frame = CGRectMake(0,20,300,300);
[scroll addSubview:myView];
}
Only thing I want to do is that I want to see MyView (green background with one label) inside the ScrollView. If I run this app, scrollview is empty, nothing is shown inside.
Probably it is a stupid question but as I said, I am newbie at this. I have a PageControll app from Apple but for me this is quite complicated code in my iOS beginning. Of course I was googling... Please tell me where is a problem. Thank you
Where do you define scroll? Is it a property of your viewcontroller?
I think the problem here is that you don't add scroll view to your viewcontroller's view with
[self.view addSubview:scroll]
Everything seems to right. Just retain the view in your class that you are receiving from the following line and add that as a subview to your scrollview.
self.myView = [nibContents objectAtIndex:0];
Best Regards.

Resources