View is not autoresizing to meet constraints - swift

I have a hard time wrapping my head around this problem. I have included a simple example where I set constraints to be wrapping the device. However, the view frame is not resizing based on different devices.
Iphone XR -> See how frame is different than holderView
https://imgur.com/ZyoCNGQ
Iphone 8 -> Frame is same size as holderView
https://imgur.com/S6xnzcZ
Ideally the holder height and width should change with different devices, however, that is not happening.

Your layout seems to look fine.
If they look fine in the simulator, they are fine.
I suspect that you are printing your frames before the layout gets properly set.
Try moving the debugging print statements to viewDidLayoutSubviews or viewDidAppear, from viewDidLoad.
Also, if you want to wrap the entire device, you need to set constraints around the superView, not safeAreaLayoutGuide.
Although I've done it programmatically, the following is basically the layout you have:
class ViewController: UIViewController {
var holderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
holderView = UIView()
holderView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(holderView)
NSLayoutConstraint.activate([
holderView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
holderView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
holderView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
holderView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
holderView.backgroundColor = .orange
print(holderView.frame)
print(view.frame)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(holderView.frame)
print(view.frame)
}
}
In iPhone XR,
(0.0, 0.0, 0.0, 0.0)
(0.0, 0.0, 414.0, 896.0)
(0.0, 44.0, 414.0, 818.0)
(0.0, 0.0, 414.0, 896.0)
is printed. You can see that holderView's frame is (0,0,0,0). But it gets eventually set. Since you used a storyboard, I think the initial frame size is set to the canvas frame size.

Related

How to make mapView and segmentedControl move relative to AdMob adaptive banner

I'm adding an adaptive banner ad from AdMob to the bottom of a mapView in my iOS app. No problem adding it, but the banner obscures both a segmentedControl which is placed at the bottom of the mapView. (Both the mapView and the segmentedControl are inside a tabBarController, all created in Storyboard.) The ad also covers the Apple Mags logo and "Legal" information - not sure if Apple will have a problem with that.
Image example - top of segmentedControl barely visible under ad
How can I make the mapView shift up by the same amount of space taken up by the ad, so as to make the segmentedControl and Apple Maps boilerplate visible, and not shift up at all if no ad is loaded? (Or is there another best practice of handling this?)
All my AdMob code is from the official AdMob tutorial:
func getAdaptiveSize() -> GADAdSize {
var frame: CGRect
if #available(iOS 11.0, *) {
frame = view.frame.inset(by: view.safeAreaInsets)
} else {
frame = view.frame
}
let viewWidth = frame.size.width
return adSize
}
func loadAdaptiveBannerAd(){
bannerView.adSize = getAdaptiveSize()
bannerView.load(GADRequest())
}
func addBannerToView(){
bannerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerView)
NSLayoutConstraint.activate([
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
bannerView.adUnitID = "ca-app-pub-3940256099942544/2435281174"
bannerView.rootViewController = self
bannerView.backgroundColor = .darkGray
addBannerToView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadAdaptiveBannerAd()
}
My hunch is that I have to fix this not by changing the placement of the ad banner itself, but by approaching either the mapView or the superview somehow, but I haven't found the solution.
You didn't show your actual layout, but I'm going to assume it looks very close to this:
The Bottom of your mapView is constrained to the Bottom of the safe-area, and the Bottom of your Segmented control is constrained to the Bottom of the mapView. If you have your SegControl also constrained to the safe-area, change that to the mapView -- that way we only have to manipulate the mapView and the SegControl will move with it.
First step, change the Priority of the Map view's bottom constraint to High (750):
and your constraints will look like this:
Next, connect that constraint to an #IBOutlet:
#IBOutlet var mapBottomConstraint: NSLayoutConstraint!
result:
Now at run-time, when you add your banner view, add a NEW Bottom constraint to your map view:
func addBannerToView(){
bannerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerView)
NSLayoutConstraint.activate([
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// add this line
mapView.bottomAnchor.constraint(equalTo: bannerView.topAnchor)
])
}
Because the newly created constraint will have the default Priority of Required (1000), auto-layout will "pin" the bottom of the map view to the top of the banner. Since we gave the Storyboard-created Bottom constraint a Priority of 750, we've told auto-layout it's allowed to break that constraint without causing conflicts.
If / when you remove the banner view, that "new" constraint will be removed with it, and the Storyboard-created constraint will again be used.

Why are my subview bounds (0,0,0,0) after setting constraints?

I am using the latest version of swift and writing everything programmatically. I’m trying to create a UIView holderView that resides inside and is constrained to the bounds of the safe area of the top level view. This code returns
(0.0, 0.0, 414.0, 896.0)
(0.0, 0.0, 0.0, 0.0)
which suggests that the holderView is not constrained to the top level view. Can anyone please advise on how to proceed? Code below.
class WelcomeViewCon: UIViewController {
var holderView = UIView()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
configure()
}
private func configure() {
view.backgroundColor = .systemRed
view.addSubview(holderView)
holderView.backgroundColor = .systemGray
let constraints = holderView.constraintsForAnchoringTo(boundsOf: view)
NSLayoutConstraint.activate(constraints)
print(view.bounds)
print(holderView.bounds)
}
}
extension UIView {
/// Returns a collection of constraints to anchor the bounds of the current view to the given view.
///
/// - Parameter view: The view to anchor to.
/// - Returns: The layout constraints needed for this constraint.
func constraintsForAnchoringTo(boundsOf view: UIView) -> [NSLayoutConstraint] {
return [
topAnchor.constraint(equalTo: view.topAnchor),
leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.bottomAnchor.constraint(equalTo: bottomAnchor),
view.trailingAnchor.constraint(equalTo: trailingAnchor)
]
}
}
It’s just a matter of timing. Constraints do not take effect until after layout. But you are applying constraint during layout (which is totally wrong; this is what updateConstraints is for, or just do it all once in viewDidLoad) and so you cannot measure the results until after the next layout.
Moreover layout happens many times so your code adds the subview and the constraints over and over. Dangerous stuff.

Swift text field border isn't the right width

I have a bottom border that I generated after following the answer here.
This works absolutely great except the border isn't the right width. It's set with constraints to match the width of the button below it but as you can see is coming up short.
What am I missing?
Code :
extension UITextField
{
func setBottomBorder(withColor color: UIColor)
{
self.borderStyle = UITextBorderStyle.none
self.backgroundColor = UIColor.clear
let width: CGFloat = 3.0
let borderLine = UIView(frame: CGRect(x: 0, y: self.frame.height - width, width: self.frame.width, height: width))
borderLine.backgroundColor = color
self.addSubview(borderLine)
}
}
then in the VC :
override func viewDidLoad() {
authorNameOutlet.setBottomBorder(withColor: UIColor.lightGray)
}
Then Xcode shows...
but the simulator shows...
I've tried this both setting the width of the text field to be 0.7 x the superview width (same as the button below it) and also setting the width of the text field to be equal width of the button but neither works.
This is because of AutoLayout.
You can add autoresizingMask to your line.
borderLine.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
You are working with with a static frame for the border line view. After viewDidLoad your view controller's view gets resized.
Option 1: (Fast and dirty)
Move your code from viewDidLoad() to viewWillAppear(_ animated: Bool). viewWillAppear gets called after the first layout of your view controller's view
Option 2:
Add constraint for your border line view. So that your border line view will resize automatically.
Importent hint:
Do not forget super calls in overrides or you will get strange bugs!
E.g:
override func viewDidLoad() {
super.viewDidLoad()
// your code
}

Determine width of UIView in Swift

I have an autolayouted UIView and need to know the width of it. How do I find the width the easiest way in Swift?
You can find the width of any view by
let width = yourView.bounds.width
If you have applied a width constraint to the view and you have an outlet for that constraint then you can find it by.
let width = yourWidthConstraint.constant
The right place to get the UIScreen frame data is in viewDidLayoutSubviews as it is called after the subViews have been laid out in screen also it is called after every time your device changes orientation such as your width will be different when your user goes into landscape mode.This is called after viewWillAppear:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let viewWidth = self.myView.bounds.width
}
Inside implementation your view you can use
override func layoutSubviews() {
super.layoutSubviews()
// you can get width of view in this function
print(self.frame.size.width)
}

Set scrollview size and position by code

I've added a scrollView in Main.storyboard and made it the same size as the view and set the backgroundcolor to blue.
But I'm having troubles with autolayout so I'm trying to deactivate the constraints by code (succeeded) and set the frame position and size with the following code inside viewDidLoad()
NSLayoutConstraint.deactivateConstraints(self.view.constraints())
self.scrollView.frame = CGRectMake(0.0, 100.0, 200.0, 200.0)
self.scrollView.backgroundColor = (UIColor.redColor())
When I run the code the colour changes to red (so I've got that going for me, which is nice) but the scrollView doesn't change position or size.
What is the right syntax to do this?
This will work if you set the frame in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.scrollView.frame = CGRect(x: 0.0, y: 100.0, width: 200.0, height: 200.0)
}
Trying to do this in viewDidLoad() will not work because scrollView is not yet in the view hierarchy at this point.
Note that you are only deactivating the constraints for the view and not its subviews in your code snippet. This may not be what you intended.
Also note that I'm not endorsing this as a way to fix constraint problems. ;-)