I'm trying to create a uiview programmatically and adding it to a stack_view which was also programmatically created and added to the view.
This is the code:
the .h
#interface ViewController : UIViewController
#property (strong, nonatomic, nullable) UIStackView * stack;
#end
the .m
#implementation viewController
#synthesize stack;
- (void) viewDidLoad {
[super viewDidLoad];
CGRect * vframe = self.view.frame;
// - Option 1
stack = [[UIStackView alloc] initWithFrame:CGRectMake(vframe.origin.x, 100,vframe.size.width,300)];
stack.axis = UILayoutConstraintAxisHorizontal;
stack.aligment = UIStackViewAligmentTop;
stack.distribution = UIStackViewDistributionFill;
[self.vew addSubview:stack];
// Option 2
// stack.traslateAutorezisingMaskIntoConstraints = NO;
// [stack.leadingAnchor constraintsEqualToAnchor: self.view.leadingAnchor].active = YES;
// [stack.topAnchor constraintsEqualToAnchor: self.view.topAnchor constant: 100].active = YES;
UIView * pView = [[UIView alloc] init];
pView.backgroundColor = [UIColor blueColor];
[stack addArrangedSubview:pView];
}
#end
This code does not show the view at all, I've tried to prove the option 2 (appeared commented in the code) and it does not work either. It is not supposed that the view, upon inserted in the stack, will get the size of the stack, since the distribution of it is "Fill"?. None of this work either even if I define Pview with frame=CGRectMakeRect(0,0,self.view.frame.size.width,100), for instance.
What am I doing wrong?
EDIT: I already fix the misspelled in the code (the * in the CGRect and the self.vew instead of self.view). I made these mistakes when I was manually copying the code, I did not copy and paste the code; tha's is why is made them and that's why the original code compile well
To diagnose this first test the pView. Init it with a fixed size like CGRectMake(100,100,100,100) and add it directly to the viewContoller's view. You should be able to see it no problem if not you have a deeper issue.
If that goes well try the UIStackView with a fixed size. Color its background to see it better if you need. If you still dont see it then double check it still has the correct frame in viewDidAppear of the viewController. It might have adjusted itself after creation. If that is correct go to Debug -> View Debugging -> Capture View Heirachy in Xcode after you have it up in the simulator. If you dont see it there then there was an issue adding it as a subview (note the typo [self.vew addSubview:stack];)
If that goes well then there is a problem with [stack addArrangedSubview:pView]. Similar to the previous step, loop through all the arrangedSubviews in UIStackView in viewDidAppear of its viewController.
CGRect vframe = self.view.frame;
In viewDidLoad, your self.view.frame has not been calculated yet. You need to do this in viewDidLayoutSubviews or viewDidAppear. both of these will get called multiple times so be careful.
EDIT:
As suggested below by danh (I overlooked it) you should remove * from the above line , also, you have several misspelling in your code, don't know how this code really even compiled for you.
Related
I have a UIView subclass that is added as an arranged subview of a UIStackView. Depending on the data in the model, I want to either hide or show the arranged subview (called myView), but the problem is that when I go to hide it, even if I set myView.hidden = NO, it still shows that myView.hidden = YES.
For example, the following is the code that I have. It starts out with the view being hidden and depending on whether or not myModel.someProperty is set, it will show myView. Or that is what is supposed to happen.
I have set a breakpoint and stepped through this code and used LLDB to verify that self.myView.hidden == YES before line 4 is executed. I then checked the value right after stepping over line 4 and it was still YES. But line 4 explicitly sets it to NO and nothing in the implementation of myView overrides or even sets or checks the hidden property of itself. So setting hidden on this just goes to the standard UIView setHidden: method. So how could it still be YES?
1. //currently, self.myView.hidden is YES
2.
3. if (self->_myModel.someProperty) {
4. self.myView.hidden = NO;
5.
6. //for some reason, self.myView.hidden is still YES
7.
8. while (self.myView.isHidden) {
9. NSLog(#"myView is hidden, but it should not be");
10. self.myView.hidden = NO;
11. }
12. NSLog(#"myView is no longer hidden");
13. }
I added a loop on line 8 that will cause the view to be hidden again. It works this time. So if I set myView.hidden = NO two times, then it actually will get set to NO. But if I only set it one time, then it stays at YES. I do not understand what is going on.
Does anyone know what might be wrong here or how to troubleshoot this further? I have used LLDB's po command to view the value of myView.isHidden before and after each set of the property. So before line 4, it was set to YES, which is correct. Then, after line 4, I checked it and it was still set to YES, even though it was explicitly set to NO on the previous line. Then, I checked and it entered the loop on line 8 (even though it should not have if it would have been non-hidden like it should have been). And then I checked again before line 10 and myView.hidden was still YES and I checked after line 10 and it was finally correctly set to NO.
But I am just not sure what is going on. This is very counterintuitive as I am explicitly setting it to NO, but it is not getting set until I set it twice to NO.
Is there a good way to troubleshoot this or to figure out what is wrong or does anyone have any suggestions on what might be the problem?
Update
I have updated the code to add some extra log statements. I have also used p self.myView.hidden when checking that property in LLDB.
1. // at this point, self.myView.hidden = YES
2.
3. if (self->_myModel.someProperty) {
4. NSLog(#"Before setting hidden=NO: %#", self->_myView);
5. self.myView.hidden = NO;
6. NSLog(#"After setting hidden=NO: %#", self->_myView);
7.
8. while ([self.myView isHidden]) {
9. NSLog(#"SHOULD NOT BE HERE - Before setting hidden=NO again: %#", self->_myView);
10. self.myView.hidden = NO;
11. NSLog(#"SHOULD NOT BE HERE - After setting hidden=NO again: %#", self->_myView);
12. }
13.
14. NSLog(#"Finally, no longer hidden: %#", self->_myView);
15. }
Here are the log statements from this code. The first log statement is correct, as it shows myView.hidden == YES. The second log statement, however, seems wrong to me because it is still showing myView.hidden == YES even though on the previous line it was just set to NO.
Before setting hidden=NO: <MyView: 0x117ef6eb0; frame = (0 49.6667; 123.667 20.3333); hidden = YES; layer = <CALayer: 0x280ddaa20>>
After setting hidden=NO: <MyView: 0x117ef6eb0; frame = (0 49.6667; 123.667 20.3333); hidden = YES; layer = <CALayer: 0x280ddaa20>>
The next set of log statements are inside the loop, which it should not even enter anyway since I am setting myView.hidden to NO, but it goes in anyway because the value is still YES. And here it looks like it works correctly. The first log statement shows it is visible and then the next log statement shows it is hidden.
SHOULD NOT BE HERE - Before setting hidden=NO again: <MyView: 0x117ef6eb0; frame = (0 49.6667; 123.667 20.3333); hidden = YES; layer = <CALayer: 0x280ddaa20>>
SHOULD NOT BE HERE - After setting hidden=NO again: <MyView: 0x117ef6eb0; frame = (0 49.6667; 123.667 20.3333); layer = <CALayer: 0x280ddaa20>>
Finally, no longer hidden: <MyView: 0x117ef6eb0; frame = (0 49.6667; 123.667 20.3333); layer = <CALayer: 0x280ddaa20>>
Update 2
I know this code seems to be working on its own, but it is not working for me in my project. I will show the code for my view class here and also the output from a debug session showing the same behavior observed in the code.
And I know it might be in my code, but at the same time, I just do not see how. All my code consists of here is a call to setHidden:. Nothing extra. Before calling setHidden, the value of hidden is YES. After calling setHidden:NO, the value is still YES. I do not understand that. I am wondering if this is maybe a compiler issue. I know these compilers are very well tested, but at the same time I also do not understand how it is my code. I am simply setting hidden = NO, but it is not working unless I do it twice.
Debug Session
Here is the output from LLDB. I set a breakpoint right before the view was about to be unhidden (line 3 in the previous code snippets). At this point, myView.hidden = YES.
So all I did was to print the value of hidden for that view, and it correctly showed YES. After this, I ran call self.myView.hidden = NO to try to update it, but that doesn't work as can be seen in the debug statement that is printed out right below the call statement. It still shows hidden = YES;. I also went ahead and printed the value again just to be sure, and it still shows hidden = YES.
(lldb) p self.myView.hidden
(BOOL) $12 = YES
(lldb) call self.myView.hidden = NO
<MyView: 0x12b138980; frame = (0 49.6667; 123.667 20.3333); hidden = YES; layer = <CALayer: 0x283addfe0>> MyView::setHidden:NO
(BOOL) $13 = NO
(lldb) p self.myView.hidden
(BOOL) $15 = YES
Next, I just set the value to NO again and this time it works as can be seen by the debug statement and I also printed the value again for good measure.
(lldb) call self.myView.hidden = NO
<MyView: 0x12b138980; frame = (0 49.6667; 123.667 20.3333); layer = <CALayer: 0x283addfe0>> MyView::setHidden:NO
(BOOL) $16 = NO
(lldb) p self.myView.hidden
(BOOL) $17 = NO
Here is the code for my view class that gets shown and hidden. I am not overriding or doing anything with the hidden property, so any call to setHidden: goes straight to the method on UIView.
MyView.h
#import <UIKit/UIKit.h>
#import "MyModel.h"
#interface MyView : UIView
#property (strong, nonatomic, nullable) MyModel *myModel;
#end
MyView.m
#import "MyView.h"
#interface MyView ()
#property (strong, nonatomic) UILabel *label;
//other UI components are here, but they are just more labels and an image view
#end
#implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.label];
//add other labels and the image view
[NSLayoutConstraint activateConstraints:#[
[self.label.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor],
[self.label.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor],
[self.label.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
//more constraints for the other labels and the image
]];
}
- (void)setMyModel:(MyModel *)myModel {
self->_myModel = myModel;
[self updateDisplay];
}
- (void)updateDisplay {
//set the text of all the labels based on the model
}
- (UILabel *)label {
if (!self->_label) {
self->_label = [[UILabel alloc] init];
self->_label.translatesAutoresizingMaskIntoConstraints = NO;
self->_label.numberOfLines = 0;
self->_label.text = #"My Text:";
[self->_label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[self->_label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
}
return self->_label;
}
#end
Please let me know if there is anything else that I should post that would help or if there is anything I could try. I can just write the value twice in my code, but without understanding why I have to do it, I feel that is sort of dangerous because how do I know that two times will always be sufficient? Plus, it is just weird to have to set a variable to the same value twice in a row for it to work.
Thank you to everyone for your help with this.
Yes, there is a bug / quirk when animating the showing / hiding of arranged subviews in a UIStackView.
You should be able to correct the issue by adding this to your custom view class:
- (void)setHidden:(BOOL)hidden {
if (self.isHidden != hidden) {
[super setHidden:hidden];
}
}
Here is a complete example that shows the problem, and shows the "fix": https://github.com/DonMag/StackViewBug
It looks like this is due to a bug in the UIStackView where if you hide a view more than once, it accumulates the hidden count for that view. So, for example, imagine a view hierarchy like this:
UIStackView
MyView
Then, if you set MyView hidden=YES three times, it will take three times of setting hidden=NO to allow it to actually be set to NO. This does not appear to be an issue going the other way. So if you set it hidden=NO three times, you can set it to hidden=YES just once and it will be hidden.
There is more information in this StackOverflow answer:
https://stackoverflow.com/a/45599835/5140550
I am not sure if this bug has been reported to Apple or not, but it appears to be a bug in UIStackView. Now I just need to figure out a clean way to handle this issue in my code.
I'm new to iOS layouts, and I'm trying to programmatically centre a custom view (actually a React Native RCTRootView which extends UIView, if that's relevant) which has intrinsically sized, dynamic content (defined over in javascript land).
I've seen solutions overriding intrinsicallySizedContent but that seems to be only relevant to auto-layout, which isn't used by RN. The only way I've been able measure the size of the RCTRootView's content is by adding it to a view, and waiting over several passes of layoutSubViews until its frame has a size. That led me to the following effort:
Attempt: Wrap and set wrapper size in layoutSubViews
I'm wrapping the RCTRootView in another UIView and trying to override layoutSubViews, setting the size of my wrapping frame once I have the size of the react native content.
My wrapper is created and added to the navigation bar in my UIViewController like this:
RCTBridge *bridge = ((RCTRootView*)self.view).bridge;
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:navBarCustomView initialProperties:initialProps];
RCCCustomTitleView *titleView = [[TitleWrapperView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView];
self.navigationItem.titleView = titleView;
self.navigationItem.titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
And the wrapper is implemented like this:
#import "TitleWrapperView.h"
#import <React/RCTRootView.h>
#interface TitleWrapperView ()
#property (nonatomic, strong) RCTRootView *subView;
#end
#implementation TitleWrapperView // Extends UIView
-(instancetype)initWithFrame:(CGRect)frame subView:(RCTRootView*)subView {
self = [super initWithFrame:frame];
if (self) {
self.subView = subView;
self.subView.sizeFlexibility = RCTRootViewSizeFlexibilityWidthAndHeight;
self.clipsToBounds = true;
// Prevent the wrapper from being so large initially as to squash the '< Back' button down to '<'
self.frame = CGRectZero;
[self addSubview:subView];
}
return self;
}
-(void)layoutSubviews {
[super layoutSubviews];
CGRect contentViewFrame = self.subView.contentView.frame;
if (contentViewFrame.size.width > 0) {
// Once we have a measurement for the sub-view's content, set the wrapper's frame
if (self.frame.size.width != contentViewFrame.size.width) {
self.frame = contentViewFrame;
}
}
}
#end
That works a treat on iOS 11+, but not on iOS 10.3, where when the screen containing the custom nav (the red box) is pushed, the navigation transition animates the titleView over towards the top left, rather than into the centre (the end position, which is correct) as I'd hope:
Other approaches?
There might be a way to overcome the iOS 10 glitch above (and I'd love to hear it) but I'm fairly certain there must be a better approach. Is is possible to allow an arbitrary, complex view to lay itself out and get its measurement before adding it to a parent? I've also tried overriding sizeThatFits in my wrapper, which is called by the navigation bar, and requesting the size of my RCTRootView using [subView sizeThatFits: myLargeContainer], but I get 0,0 back.
I'm at a loss - any pointers are appreciated.
I have an project using autolayout,
And I notice that after viewWillAppear, viewWillLayoutSubViews and viewDidLayoutSubViews pair will be called several times on iOS 8, for my case, it is 2-3 times usually.
The fist viewDidLayoutSubViews will get incorrect frame size, so I have to avoid for first viewDidLayoutSubViews, and init my views afterwards.
However, when I tested it on iOS 7, I found that only ONE viewWillLayoutSubViews and viewDidLayoutSubViews pair got called, so my code broke again.
My question is, what is changed on iOS 8 for this behaviour?
EDIT:
I have pasted my demo code here:
In the code, _pieChart will be added to self.ChartViewCanvas, and self.ChartViewCanvas is using autolayout. _pieChart is from old project code, which is drawn without auto layout.
I was required to draw the pie chart before viewDidAppear, because drawing in viewDidAppear will have a 1 sec delay compare to other views in storyboard. This is not allowed for me.
Is there any way to know when is the final viewDidLayoutSubViews? Calling [self.ChartViewCanvas addSubview:_pieChart]; multiple times will lead to lower performance, and sometimes _pieChart's drawInRect will not be called every time, so the chart is not update.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_pieChart.delegate = self;
if (!_pieChart) {
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}else {
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
//_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
if ([_pieChart superview]) {
[_pieChart removeFromSuperview];
}
[self.ChartViewCanvas addSubview:_pieChart];
}
Probably only Apple knows, but I won't deal with that too much if everything is working fine. In iOS8 Apple changed a lot view controllers (again) in they way they are presented from containers VC as for rotation and UITraitCollections.
For instance UIAlertView is now a view controller, when you show one you trigger all the mechanism related to present a VC.
If this fact is creating an issue it must be said that you should not rely on how many times those methods are called because they were always be unpredictable there are too many variables to be taken into account.
A quick and dirty solution could be wrap your code in a dispatch_once if you want that it will be called only one time.
If you add your view using auto layout correctly you won't see any sort of bug.
[EDIT]
Here is a little snippet about how it might look your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
//.. your stuff
//We don't need any frame autolayout wil take care of calculating it on its pass
_pieChart = [[PieChartView alloc]initWithFrame:CGRectZero];
_pieChart.delegate = self;
_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
[self.ChartViewCanvas addSubview:_pieChart];
NSDictionary *bindings = NSDictionaryOfVariableBindings(_pieChart);
// We create constraints to tell the view that it needs to sctretch its bounds to the superview
NSString *formatTemplate = #"%#:|[_pieChart]|";
for (NSString * axis in #[#"H",#"V"]) {
NSString * format = [NSString stringWithFormat:formatTemplate,axis];
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings];
[_pieChart.superview addConstraints:constraints];
}
// Do any additional setup after loading the view.
}
Of course that is going to call drawRect:, draw rect is called when a view is marked as dirty in the display pass, but before display is usually called the autolayout engine to calculate frames of views in needs for layout.
I tried this out on my application and found the same as you: 1 call on iOS7 and 3 on iOS8. From the stack traces this seems to be down to doing double layout after viewWillAppear and an extra layout following viewDidAppear not seen on iOS7.
My suggestion would be that you add any views in viewDidLoad (or viewWillAppear), then only do layout adjustments in the layout subview runs. Based on your updated post something like:
- (void)viewDidLoad{
[super viewDidLoad];
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
[self.ChartViewCanvas addSubview:_pieChart];
_pieChart.delegate = self;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
For interest the difference between iOS7 and 8 calling sequence was:
iOS7
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
ii) viewDidAppear is called.
iOS8
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
iii) exact same layout with exact same stack is called again. So something in the stack must request a rerun from some point.
iv) viewDidAppear is called.
v) An extra layout of subviews is called. This seems driven from a transaction pushed onto the run loop.
Can I have some UIView which will always appear on top in iOS?
There are lots of addSubview in my project but I need to have one small view which will always appear. SO is there any other option than
[self.view bringSubViewToFront:myView];
Thanks
One more option (especially if you want to overlap several screens, with logo for example) - separate UIWindow. Use windowLevel to set the level of new window.
UILabel *devLabel = [UILabel new];
devLabel.text = #" DEV ";
devLabel.font = [UIFont systemFontOfSize:10];
devLabel.textColor = [UIColor grayColor];
[devLabel sizeToFit];
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
static UIWindow *notificationWindow;
notificationWindow = [[UIWindow alloc] initWithFrame:
CGRectMake(screenSize.width - devLabel.width, screenSize.height - devLabel.height,
devLabel.width, devLabel.height)];
notificationWindow.backgroundColor = [UIColor clearColor];
notificationWindow.userInteractionEnabled = NO;
notificationWindow.windowLevel = UIWindowLevelStatusBar;
notificationWindow.rootViewController = [UIViewController new];
[notificationWindow.rootViewController.view addSubview:devLabel];
notificationWindow.hidden = NO;
Another option is set layer.zPosition of your UIView.
You need to add
#import <QuartzCore/QuartzCore.h>
Framework to your .m file.
And set such like
myCustomView.layer.zPosition = 101;// set maximum value as per your requirement.
For more information about layer.zPosition read this documentation.
Discussion
The default value of this property is 0. Changing the value of this property changes the the front-to-back ordering of layers onscreen. This can affect the visibility of layers whose frame rectangles overlap.
The other option is to add other subviews below this always-on-top subview. For example:
[self.view insertSubview:subview belowSubview:_topSubview];
There's no solution with Interface Builder if you search for this kind. It should be done programmatically. If you don't want to use bringSubviewToFront: everytime, just insert other subviews below this one.
Many times your view did not appear in viewDidLoad or, if your view comes from parentViewController (for example in many transitions like modal segue..) your can see parentViewController only in viewDidAppear so:
Try to put bringSubviewToFront in :
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.view bringSubViewToFront:myView];
// or if your view is attached in parentViewController
[self.parentViewController.view bringSubViewToFront:myView];
}
Good luck!
tutorialImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Tap to Start.png"]];
tutorialImage.frame = CGRectMake(0, 0, 1024, 768);
[tutorialImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(blankMethod)]];
tutorialImage.userInteractionEnabled = YES; // i use this line and the previous line so that the user can't press any buttons behind the image
tutorialImage.alpha = 0;
[self.view addSubview:tutorialImage];
[self.view bringSubviewToFront:tutorialImage];
[UIView animateWithDuration:1.0f animations:^{
tutorialImage.alpha = 1;
} completion:^(BOOL finished) {
[self.view addSubview:tutorialImage]; // this line makes the image come back
}];
I know you probably won't be able to deduce the problem just from this code, but is there anything in that code that makes the tutorialImage auto remove itself from it's superview?
Anyway, during the UIView animation the image fades in for a bit like normal, then it disappears. If I add that last line of code there (the commented one), the UIView animation will make the image fade in and flash once halfway through. I just added this image and there is no code telling it to remove itself from superview.
Let me know if you have any ideas as to fixing the problem or showing you more code, I'll check frequently.
Also, I've tried restarting the simulator which didn't work, and the tutorial image is declared in the h file UIImageView *tutorialImage;. The console doesn't show any errors or anything when the problem occurs or anything.
Edit:
Ok, strange. I altered the declaration in the H file from UIImageView *tutorialImage; to #property (strong, nonatomic) UIImageView *tutorialImage; then used _tutorialImage fixed the problem. Is this something to do with the strong parameter? I'll mark who ever can explain what was going on as correct.
When you have a weak reference, ARC will dealloc the object once there are no more retains on it (when no object is pointing at the object with a strong pointer). When you changed the #property to strong, you are now telling ARC to keep the object around until the parent (your view controller) is dealloc'ed.