My app receives a measurement every minute. When the measurement is 0, I want a label to be displayed in the middle. When it is greater than that, when I want the label to disappear. The issue I have is that once the label appears, it doesn't hide once I set its hidden mode to true.
UILabel *emptyBagLabel = [[UILabel alloc] init];
emptyBagLabel.textAlignment = NSTextAlignmentCenter;
emptyBagLabel.textColor = [UIColor darkGrayColor];
emptyBagLabel.numberOfLines = 0;
emptyBagLabel.text = #"EMPTY POUCH";
emptyBagLabel.translatesAutoresizingMaskIntoConstraints = NO;
emptyBagLabel.hidden = true;
[self addSubview:emptyBagLabel];
[emptyBagLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor].active = YES;
[emptyBagLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor].active = YES;
[emptyBagLabel.widthAnchor constraintEqualToAnchor:self.widthAnchor].active = YES;
[emptyBagLabel.heightAnchor constraintEqualToConstant:100].active= YES;
if (measurement == 0 || measurement <= 0.005) {
emptyBagLabel.hidden = false;
}
if (measurement > 0.005) {
emptyBagLabel.hidden = true;
}
I've been scratching my head at this for a while, a bit frustrated with how I'm not able to solve such a trivial issue. The method I have it in is called every minute. I know the method and the hidden = true line are getting called, so I'm unsure as to what is causing the issue.
Subview that are added programmatically ought to be added lazily, so you get exactly one subview instance. I'd refactor the code like this...
- (UILabel *)emptyBagLabel {
UILabel *emptyBagLabel = (UILabel *)[self.view viewWithTag:128];
if (!emptyBagLabel) {
emptyBagLabel = [[UILabel alloc] init];
emptyBagLabel.tag = 128;
// set all of the attributes her for how the label looks, textColor, etc.
// everything except the properties that change over time
[self addSubview:emptyBagLabel];
}
return emptyBagLabel;
}
// if this view is built using initWithFrame...
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self emptyBagLabel]; // to add the label initially
}
return self;
}
// if this view is ever loaded from a nib, then
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self emptyBagLabel];
}
return self;
}
// elsewhere in the view, when you get the measurement
// side note: alpha is usually a better choice than hidden because you can animate it
if (measurement == 0 || measurement <= 0.005) {
self.emptyBagLabel.alpha = 1.0;
}
if (measurement > 0.005) {
self.emptyBagLabel.alpha = 0.0;
}
Related
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.
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
I have a weird issue with a UIStepper (and it's accompanying UITextField)
Consider this code snippet:
#interface LTRPageTracker : UIView <UITextFieldDelegate>
{
UIStepper* page_move;
UITextField* page_no_view;
}
-(void) nextOrPrevPage:(id)sender forEvent:(UIControlEvents) event;
#implementation LTRPageTracker
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
CGRect stepperFrame, pageNoframe;
pageNoframe.origin = frame.origin;
pageNoframe.size.height = frame.size.height;
pageNoframe.size.width = frame.size.width/2;
stepperFrame.origin.x = pageNoframe.origin.x + pageNoframe.size.width +1;
stepperFrame.origin.y = frame.origin.y;
stepperFrame.size = pageNoframe.size;
page_move = [[UIStepper alloc] initWithFrame:stepperFrame];
[page_move setMinimumValue:0];
[page_move setValue:7];
page_move.maximumValue =1000;
[page_move addTarget:self action:#selector(nextOrPrevPage:forEvent:) forControlEvents:UIControlEventValueChanged];
page_no_view = [[UITextField alloc] initWithFrame:pageNoframe];
page_no_view.delegate = self;
page_no_view.backgroundColor = [UIColor redColor];
[self addSubview:page_no_view];
[self addSubview:page_move];
[page_move sendActionsForControlEvents:UIControlEventValueChanged];
[page_move setEnabled:YES];
}
return self;
}
-(void) nextOrPrevPage:(id) sender forEvent:(UIControlEvents) event {
//assert(sender == page_move);
NSLog(#"Event is %x", event);
page_no_view.text = [[NSNumber numberWithDouble: page_move.value] stringValue];
}
I have added this View to the navigation bar.
And I can decrement the value of the UIStepper but no increment it (the event will simply not get triggered for increment but will do so decrement).
WHY?
Using iOS 7, running on simulator.
Whenever you have a problem with a UI element not responding, the first thing you should check is whether it (or part of it) is outside the bounds of its superview (the part that's out of the bounds will not respond). An easy way to do this, is to give the superview a background color. In the init method assign a background color to self, and see what that shows you. I'm betting that you'll see that the right side of the stepper is not within its superview, so you'll need to make that view bigger, or change the stepper's position within that view.
If you make the view's frame 150x30, and change these two lines in the view's init method, I think it should work ok for a number as large as 1000:
pageNoframe.size.width = frame.size.width/4; // changed 2 to 4
stepperFrame.origin.x = pageNoframe.origin.x + pageNoframe.size.width+20; // changed 1 to 20
I have created a subclass of UITableViewCell because I want to explicitly set the imageView frame size, regardless of the size of the images I actually give it.
That being said, when my tableView loads, everything seems to shift after a split second. The image gets slightly larger, and the titleLabel and detailLabel both shift over closer to the image. Have I done something wrong in my subclass?
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Selected View
self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.frame];
self.selectedBackgroundView.backgroundColor = RGB(233, 233, 233);
// Text Label
self.textLabel.textColor = [UIColor darkGrayColor];
self.textLabel.opaque = YES;
self.textLabel.highlightedTextColor = self.textLabel.textColor;
// Detail
self.detailTextLabel.font = [UIFont systemFontOfSize:12.0];
self.detailTextLabel.numberOfLines = 3;
self.detailTextLabel.opaque = YES;
self.detailTextLabel.highlightedTextColor = self.detailTextLabel.textColor;
// Cell
self.opaque = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)layoutSubviews {
[super layoutSubviews];
float desiredWidth = 70;
float desiredHeight = 70;
float leftMargin = 5;
float topMargin = 5;
float w=self.imageView.frame.size.width;
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.clipsToBounds = YES;
if (w>desiredWidth) {
float widthSub = w - desiredWidth;
self.imageView.frame = CGRectMake(leftMargin,topMargin,desiredWidth,desiredHeight);
self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x-widthSub,self.textLabel.frame.origin.y,self.textLabel.frame.size.width+widthSub,self.textLabel.frame.size.height);
self.detailTextLabel.frame = CGRectMake(self.detailTextLabel.frame.origin.x-widthSub,self.detailTextLabel.frame.origin.y,self.detailTextLabel.frame.size.width+widthSub,self.detailTextLabel.frame.size.height);
}
}
I see you are doing
self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.frame];
I really think it should be
self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.bounds];
because self.selectedBackgroundView is positioned based on parent view coordinate system
Thank you for looking, I try to implement in the super view (view controller) a custom view I created.
fromAge = [[simpleFormCell alloc]initWithFrame:CGRectMake(0, spaceFromTopLabel + 190, 200, 30)];
fromAge.title = #"Age";
fromAge.placeholder = #"";
fromAge.widthOfTextField = 60;
[fromAge populateView];
fromAge.textField.delegate = self;
fromAge.textField.tag = fromAgeTextFieldTag;
[fromAge.textField setInputView:multipurposePicker];
[scrollViewBackground addSubview:fromAge];
toAge = [[simpleFormCell alloc]initWithFrame:CGRectMake(0, spaceFromTopLabel + 190, 200, 30)];
toAge.title = #"to";
toAge.placeholder = #"";
toAge.widthOfTextField = 60;
toAge.leftPaddingForLabel = 210;
toAge.leftPaddingForTextField = 240;
[toAge populateView];
toAge.textField.delegate = self;
toAge.textField.tag = toAgeTextFieldTag;
[toAge.textField setInputView:multipurposePicker];
[scrollViewBackground addSubview:toAge];
simpleFormCell is a custom view with the following implementation:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
title = #"test";
placeholder = #"test2";
numberofLine = 1;
leftPaddingForLabel = 20;
leftPaddingForTextField = 140;
widthOfLabel = 90;
widthOfTextField = 160;
height = 30;
//[self populateView];
}
return self;
}
- (void)populateView{
label = [[UILabel alloc]initWithFrame:CGRectMake(leftPaddingForLabel, 0, widthOfLabel, (height * numberofLine))];
label.text = title;
label.textAlignment = UITextAlignmentLeft;
label.backgroundColor = [UIColor clearColor];
[self addSubview:label];
textField = [[UITextField alloc]initWithFrame:CGRectMake(leftPaddingForTextField, 0, widthOfTextField, (height * numberofLine))];
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = placeholder;
textField.textAlignment = UITextAlignmentCenter;
textField.tag = tag;
[textField setReturnKeyType:UIReturnKeyNext];
[self addSubview:textField];
}
I have a similar method that called [self populateView] in the init. That seems to work fine. Please assume that multipurposePicker is working correctly. This is the code but wont apply to these two instances.
- (id)initWithPreset1:(CGRect)frame title:(NSString *)titleValue numberOfLine:(int)numberOfLineValue delegate:(id) delegateValue tag:(int)tagValue{
self = [super initWithFrame:frame];
if (self) {
title = titleValue;
placeholder = #"";
numberofLine = numberOfLineValue;
leftPaddingForLabel = 20;
leftPaddingForTextField = 140;
widthOfLabel = 110;
widthOfTextField = 160;
height = 30;
tag = tagValue;
[self populateView];
textField.delegate = delegateValue;
}
return self;
}
These white boxes wont respond to my touch. (textFieldDidBeginEditing wont fire).
[fromAge becomeFirstResponder] works. It just wont respond to my touch. I made sure that there is nothing on top of it. Please help. Thank you!
I found the problem! Anna Karenina's comment guided me to the right solution. This has nothing to do with view hierarchy as I first thought. What happened was toAge was blocking fromAge. This makes fromAge textfield non-responsive. As per Anna's comment, eventhough toAge textfield was shown, it was out of the view that supports it. This prevents it from accepting any touches. Thus neither boxes receive any touches. Thank you Anna!