The app ran perfectly prior to updating to Xcode 8 Beta 6 and Swift 3. I've changed nothing else but now I have two problems.
First, a few random views are no longer showing up. They're just square, colored boxes. The views above them show up though.
In Interface Builder:
On simulator:
Second, my model VC is no longer appearing when segued. It did before and I can see the segue is being called but now its' not there.
If anyone can provide ideas about either problem it'd be greatly appreciated.
So between Xcode 7/Swift 2 --> Xcode 8/Swift 3, something changed with how to turn a UIView into a circle. Here is my code now:
func roundView (_ viewToRound: UIView) {
viewToRound.layer.cornerRadius = 20
//viewToRound.layer.cornerRadius = (viewToRound.frame.width/2)
viewToRound.clipsToBounds = true
}
As you can see, I've replaced my cornerRadius method to be an explicit "20" instead of inferred from the view size. With my previous "frame.width" the views were literally not showing up at all. Now they're back to normal. I don't know what changed but this definitely fixed it.
Something may have happened to the auto layout constraints. Double check that those are set properly.
Also, you don't need to use the simulator to verify this; use the Assistant editor's Preview view:
As a sanity check, the first thing I would do is reset all of the elements in your view to the suggested constraints to see if that resolves the problem.
It's definitely an AutoLayout issue in Xcode 8. The problem doesn't exist without using AutoLayout. I made this workaround using a protocol:
protocol Roundable {}
extension Roundable where Self: UIView{
func roundCorners(){
self.layer.cornerRadius = self.bounds.height / 2
}
}
class CustomView: UIView, Roundable {
override var bounds: CGRect {
didSet{
roundCorners()
}
}
}
Make the view involved a CustomView and it will show up rounded. I use a protocol here, because in this way it's easy to extend an already existing UIView subclass with the functionality to round the corners. Of course it is possible to set the cornerRadius directly in the bounds property observer.
There is an issue with iOS 10 UIView lifecycle & AutoLayout.
What I mean by that is that in methods such as viewDidLoad & viewWillAppear the frames on all UI elements are `{{0,0},{1000,1000}}.
In cases such as with setting round corners makes rounded corners with 500px & you get invisible UI components :)
How I resolved this issue is by setting the rounded corners in viewDidLayoutSubview in UIViewControllers or layoutSubviews in UIView subclasses.
Related
I have a xib. I have a view controller that loads the xib and adds it to its subview. The xib has the background color set from the xib file (from interface) to a CUSTOM COLOR SET from assets (created by me).
Everything works fine.
Now, inside my view controller, in viewDidLoad I want to override that background color with something else. The problem I found and it replicates 100% is that overriding doesn't do anything, unless I do it in viewDidAppear.
So to sum up...
custom color background set from xib... overriding in viewDidLoad not working, overriding in viewDidAppear working
Xcode default color background set from xib (any other color except from assets custom colors)... overriding in viewDidLoad WORKS...
Why in the first case I cannot override the color and in the second I can?
Is there a hidden feature that I'm missing here? You simply cannot override a view background color in viewDidLoad if the color set from xib is custom color, but if it's any other color... like white, red, black or w/e Xcode already has everything works as expected.
If it has any impact... this view controller I'm talking about gets pushed on the navigation stack. But I don't see how this can have any impact.
Reproduction Steps:
To replicate this... create a new sample project... that only has one view controller in it. Next add a custom color via assets -> new color set. Set the view controllers view background color from storyboard to the newly added color set. Then inside viewDidLoad change the background color of the view to something else... you will notice if you run the app it WON'T CHANGE if in storyboard there is a custom color set as background.
Somehow the background custom color set from storyboard overrides happen behind the scenes after viewDidLoad and before viewDidAppear.
I come late but have more info for future readers.
I just met this bug in my App for iOS 11 and iOS 12. The bug is solved by Apple since iOS 13.0.
So if you support previous version, you still need to apply the workaround from #AlanS, or not use custom colors in storyboard and xib :
func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if #available(iOS 13.0, *) {
// Fix iOS 12 storyboard color bug.
} else {
view.backgroundColor = UIColor.black
}
}
Faced with this issue recently
I had xib file for table cell and custom colors in xcasset.
Colors are single appearance (dark mode not supported).
In cell swift class I have bool variable with didSet, where few outlets are modified: view.backgroundColor and label.textColor. Their values are based on variable value. There are ternar conditions so I was sure that color will be selected correctly.
On devices with iOS 12 (checked on 12.1 and 12.4 real and simulators) color didn't change at start but only after cell reuse.
After finding this question and few experiments, I have found that:
Setting custom color in xib file (no matter, if they have dark version or not) was performed after my didSet block and overrides my conditions results. And since I have set one of possible color in xib, I though that problem in data.
So I have reset outlets colors to default in xib and now it works
In case if you have to display some default color before some conditions, I guess putting it in init methods (awakeFromNib, viewDidLoad etc) should work
This bug was fixed in 13.0+
Duplicating answer from https://developer.apple.com/forums/thread/649009
One more strange workaround that helped me is to perform color updating code on the main thread.
It worked both in viewDidLoad and awakeFromNib.
DispatchQueue.main.async {
self.setupColors()
}
func setupColors() {
//your colors updating logic//
}
I have an extremely bizarre problem. I created a UIButton and put it on my ViewController storyboard. I added a segue from the Button to another ViewController by pressing control and dragging. I also added an outlet to the corresponding ViewController file and added a border color and width (to the button). The segue and the button border work fine on the simulator but on a device the button border does not show up and the segue does not work.
The only thing I can think of that could be causing the problem is that a little while ago I accidentally deleted my Main.storyboard, so I dragged it from my trash back to my Xcode project. But I am pretty sure my app has worked on the device even after that incident. I wasn't sure if my viewDidLoad method was being fired (since that is where I set my border color and width for the button) so I added a print statement and found out that it was indeed being called. Take a look at the code below...
override func viewDidLoad() {
super.viewDidLoad()
print("Begin Setup Process...")
// Do any additional setup after loading the view.
calibrateButton.layer.borderColor = UIColor.white.cgColor
calibrateButton.layer.borderWidth = 3
}
How would I get this to work on my device as well? What I am doing wrong
Assign Your Border Styling Here...
Try adding the code to change layer properties of calibrateButton in viewDidLayoutSubviews(), i.e.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
calibrateButton.layer.borderColor = UIColor.white.cgColor
calibrateButton.layer.borderWidth = 3
}
Also, since you're giving borderColor as white, that might not be visible if the calibrateButton is placed on a white background. Try changing it to something else that is more visible, red for example.
First try in viewDidAppear(), if it works then cut and paste it in viewDidLayoutSubviews().
I finally solved the issue. The problem was that I didn't have a height constraint on my button. I am still not exactly sure why or how this issue occurred. Why couldn I see the button text but not the border? If the segue was not working there must have been a view on top of the button that was preventing it from going off. But anyway adding a height constraint solved the problem. In answering my own question I realize that the title of my question is misleading in relation to the solution of the problem. This problem, in the end had nothing to do with wether the app was running on a simulator or the device. I just assumed it was a device issue since it was working fine on my iPhone XR simulator but not my actual iPhone 6s. However the actual problem was the screen size and how my constraints adapted to it.
I'm kind of new with ios development with swift 4, so I'm testing some functionalities.
I have a tableview inside a normal UIViewController:
The space left blank is intentionally, as I have some custom tabs.
When I show a detail from a table cell element, and then press back button, the table view gets pushed down (and up). Please see this where I show the problem.
The iphone is a SE with ios10. In simulations with iOS11, this does not happen. Developing in MacOS Sierra 10.12.6 with xCode 9.2.
Any clue on how to fix this?
EDIT:
Using the view debugger, I got this:
The selected area is a UITableWrapperView element. Behind it, is the UITableView, which preserve its constrains.
set
self.tableView.bounces = false
Or put this in viewDidAppear
let offset = CGPoint.init(x: 0, y:0)
self.tableView.setContentOffset(offset, animated: false)
It can happen because of SafeArea... Check you constraint which connects tableView top with navigation bar.
You can also try placing blank UIView between Navigation bar and tableView. Your custom tabs should be in this view. So tableView will be connected with the nearest view (which you have just added) and should not bounce.
Thats a really weird behavior. I recommend you to use the Xcode view debugger. You would be able to examine dimensions and constraints values by using size inspector.
The code where you set up your tableview, try implementing it in the method viewDidLayoutSubviews(in case you are doing it in viewDidLoad).
override func viewDidLayoutSubviews() {
//mention your tableview setup code here
}
A similar thing happened to me, and this method came quite handy. Hope this helps.
Try this for iOS 11.0
tableView.contentInsetAdjustmentBehavior = .never
My app has 2 screens:
TableViewVC (no stack views here)
DetailVC (all the nested stack views here; please see link for picture: Nested StackViews Picture) -- Note, there are labels and images within these stack views.
When you press a cell in the tableview, it passes the information from the TableViewVC to the DetailVC. The problem is with hiding the specific UIStackViews in the DetailVC. I want only 2 stack views out of the various ones in the DetailVC to be hidden as soon as the view loads. So I write this code in the DetailVC to accomplish this:
override func viewDidLoad() {
super.viewDidLoad()
self.nameLabel.text = "John"
self.summaryStackView.hidden = true
self.combinedStackView.hidden = true
}
Everything looks great but Xcode give many warnings only at runtime. There are no warning in Storyboard when the app is not running. Please see link for picture of errors: Picture of Errors
Basically it's a lot of UISV-hiding, UISV-spacing, UISV-canvas-connection errors. These errors go away if I hide the same stack views in viewDidAppear but then there is a flash of the stuff that was supposed to be hidden and then it hides. The user sees the the view briefly and then it hides which is not good.
Sorry for not being able to actually post pictures instead of links, still can't do so.
Any suggestions on how to fix this? This is for an app I actually want to launch to the app store - it's my first so any help would be great!
Edit/ Update 1:
I found a small work around with this code which I put inside the second screen called DetailVC:
// Function I use to delay hiding of views
func delay(delay: Double, closure: ()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
// Hide the 2 stack views after 0.0001 seconds of screen loading
override func awakeFromNib() {
delay(0.001) { () -> () in
self.summaryStackView.hidden = true
self.combinedStackView.hidden = true
}
}
// Update view screen elements after 0.1 seconds in viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
delay(0.1) { () -> () in
self.nameLabel.text = "John"
}
}
This gets rid of the warnings about layout constraints completely from Xcode.
It's still not perfect because sometimes I see a glimpse of the views that are supposed to be hidden -- they flash really quick on the screen then disappear. This happens so quickly though.
Any suggestions as to why this gets rid of warnings? Also, any suggestions on how to improve this to work perfectly??? Thanks!
I had the same problem and I fixed it by giving the height constraints of my initially hidden views a priority of 999.
The problem is that your stackview applies a height constraint of 0 on your hidden view which conflicts with your other height constraint. This was the error message:
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x7fa3a5004310 V:[App.DummyView:0x7fa3a5003fd0(40)]>",
"<NSLayoutConstraint:0x7fa3a3e44190 'UISV-hiding' V:[App.DummyView:0x7fa3a5003fd0(0)]>"
)
Giving your height constraint a lower priority solves this problem.
This is a known problem with hiding nested stack views.
There are essentially 3 solutions to this problem:
Change the spacing to 0, but then you'll need to remember the previous spacing value.
Call innerStackView.removeFromSuperview(), but then you'll need to remember where to insert the stack view.
Wrap the stack view in a UIView with at least one 999 constraint. E.g. Top, Leading, Trailing # 1000, Bottom#999.
The 3rd option is the best in my opinion. For more information about this problem, why it happens, the different solutions, and how to implement solution 3, see my answer to a similar question.
You can use the removeArrangedSubview and removeFromSuperview property of UIStackView.
In Objective-C :
[self.topStackView removeArrangedSubview:self.summaryStackView];
[self.summaryStackView removeFromSuperview];
[self.topStackView removeArrangedSubview:self.combinedStackView];
[self.combinedStackView removeFromSuperview];
From Apple UIStackView Documentation:(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/#//apple_ref/occ/instm/UIStackView/removeArrangedSubview:)
The stack view automatically updates its layout whenever views are added, removed or inserted into the arrangedSubviews array.
removeArrangedSubview: This method removes the provided view from the stack’s arrangedSubviews array. The view’s position and size will no longer be managed by the stack view. However, this method does not remove the provided view from the stack’s subviews array; therefore, the view is still displayed as part of the view hierarchy.
To prevent the view from appearing on screen after calling the stack’s removeArrangedSubview: method, explicitly remove the view from the subviews array by calling the view’s removeFromSuperview method, or set the view’s hidden property to YES.
When the UIViewStack is hidden, the constraints automatically generated by the UIStackView will throw lots of UISV-hiding, UISV-spacing, UISV-canvas-connection warnings, if the UIStackView's spacing property has any value other than zero.
This doesn't make much sense, it's almost certainly a framework bug. The workaround I use is to set the spacing to zero when hiding the component.
if hideStackView {
myStackView.hidden = true
myStackView.spacing = CGFloat(0)
} else {
myStackView.hidden = false
myStackView.spacing = CGFloat(8)
}
I've found that nested UIStackViews show this behavior if you set the hidden property in ✨Interface Builder✨. My solution was to set everything to not hidden in ✨Interface Builder✨, and hide things in viewWillAppear selectively.
This error is not about hiding, but about ambiguous constraints. You must not have any ambiguous constraints in your view.
If you add them programmatically you should exactly understand what constraints you add and how they work together.
If you do not add them programmatically, but use storyboard or xib, which is a good place to start, make sure there are no constraint errors or warnings.
UPD: You have a pretty complex structure of views there. Without seeing the constraints is hard to say what exactly is wrong. However, I would suggest to build you view hierarchy gradually adding views one by one and making sure there are no design-time/runtime warnings.
Scroll view may add another level of complexity if you do not handle it correctly. Find out how to use constraints with a scroll view.
All other timing hacks is not a solution anyway.
I moved all UIStackView.hidden code from viewDidLoad to viewDidAppear and broken constraints problem went away. In my case all conflicting constraints were auto generated, so no way to adjust priorities.
I also used this code to make it prettier:
UIView.animateWithDuration(0.5) {
self.deliveryGroup.hidden = self.shipVia != "1"
}
EDIT:
Also needed the following code to stop it from happening again when device is rotated:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
self.deliveryGroup.hidden = false
coordinator.animateAlongsideTransition(nil) {
context in
self.deliveryGroup.hidden = self.shipVia != "1"
}
}
I fixed it by putting the hide commands in traitCollectionDidChange.
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
self.item.hidden = true
}
So, this may only help 0.000001% of users but maybe this is a clips to bounds issue.
I ran into this recently when working with UICollectionViewCell I forgot to check clips to bounds on the view I was treating as my content view. When you create a UITableViewCell in IB it sets up a content view with clips to bounds as the default.
Point is, depending on your situation you may be able to accomplish your intended effect using frames and clipping.
Put your hide commands in viewWillLayoutSubviews() {}
I did this by storing all the hidden views of the nested UIStackView in an array and removing them from the superview and arranged subviews. When I wanted them to appear again I looped through the array and added them back again. This was the first step.
The second step is after you remove the views of the nested UIStackView from the superview the parent UIStackView still doesn't adjust it's height. You can fix this by removing the nested UIStackView and adding it again straight afterwards:
UIStackView *myStackView;
NSUInteger positionOfMyStackView = [parentStackView indexOfObject:myStackView];
[parentStackView removeArrangedSubview:myStackView];
[myStackView removeFromSuperview];
[parentStackView insertArrangedSubview:myStackView atIndex:positionOfMyStackView];
If you're having issues animating HIDING AND SHOWING subviews at the same time, repeating the .isHidden instructions in the animation completion may help. See my answer here for more detail on that.
Have you tried this? Calling super after your changes?
override func viewWillAppear() {
self.nameLabel.text = "John"
self.summaryStackView.hidden = true
self.combinedStackView.hidden = true
super.viewWillAppear()
}
I'm making an iOS app with XCode 6.1, targeted operation system version is iOS 7 and iOS 8. I wanna change the border color of a UITextField, since I'm not sure how to do this using interface builder, I simply added this line in the view controller:
self.input_username.layer.borderColor = [[UIColor redColor] CGColor];
It worked as expected when running the app with simulator, now I wonder if it is possible to show the effect in storyboard? Because for what I can tell, even when executing the app, the border color becomes red, it is still black(the default color) in interface builder... I don't want to run the app every time when I made a little change for visual effect by code...
Is this what you mean? You want storyboard change as your code settings? I have't seen this before, and I don't think that's possible. Since they are two ways to the same purpose.
I think you just can see the change in storyboard only when you draw custom view, and you can use the #IBDesignable in your custom view's declaration, such as #IBDesignable class CounterView: UIView {}. Then in your storyboard, you set a view's class name as "CounterView" and change code in the override func drawRect(rect: CGRect) {} in your custom view's class, you will see the view in storyboard also be changed.