How to set UISearchBar to safeAreaLayoutGuide programatically? - ios

I have UISearchBar in the UINavigationController. The search bar appears good in portrait but in a landscape, it appears to half of screen. It doesn't cover the full width.
I tried the following but it doesn't work.
searchBar.frame = CGRect(x: 0, y: 0, width: (navigationController?.view.bounds.size.width)!, height: 64)
view.addSubview(searchBar)
let guide = self.view.safeAreaLayoutGuide
searchBar.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 0).isActive = true
searchBar.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: 0).isActive = true

I fixed it by adding the following two lines of code:
searchBar.autoresizesSubviews = true
searchBar.autoresizingMask = .flexibleWidth

Related

Whole content out of view

I have a Shuffle package added to my project (https://cocoapods.org/pods/Shuffle-iOS), the package works fine, but the problem is that even though I set cards width and height to my UIView, cards are out of UIView anyways, I tried changing the frame of my cards and set width and height to UIViews, but they are still out of UIView any solutions?
my UIView is mainView in code below
func card1(index: swipeCardData) -> SwipeCard {
let card = SwipeCard()
card.swipeDirections = [.left, .right, .up]
card.layer.cornerRadius = 12
card.layer.shadowOffset = CGSize.zero
card.layer.shadowOpacity = 1.0
card.layer.shadowRadius = 6.0
card.layer.masksToBounds = false
card.layer.borderWidth = 2
let view_bg = UIView(frame: CGRect(x: 16, y: 60, width: mainView.frame.size.width, height: mainView.frame.height)) // here is set cards width and frame to my UIView
card.content = view_bg
view_bg.layer.cornerRadius = 12
view_bg.clipsToBounds = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [self] in
let view_bg1 = UIView(frame: CGRect(x: 0, y: 0, width: view_bg.frame.size.width, height: view_bg.frame.size.height))
card.content?.addSubview(view_bg1)
let img_card_type = UIImageView(frame: CGRect.zero)
img_card_type.contentMode = .scaleAspectFit
img_card_type.translatesAutoresizingMaskIntoConstraints = false
img_card_type.isHidden = true
view_bg1.addSubview(img_card_type)
img_card_type.centerXAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerXAnchor, multiplier: 1).isActive = true
img_card_type.topAnchor.constraint(equalTo: view_bg1.topAnchor, constant: 0).isActive = true
img_card_type.widthAnchor.constraint(equalToConstant: 100).isActive = true
pictures for better understanding :
as you can see on the screenshot above the card content is out of mainView which is in the background(gray box)
the end result below
The reason is you are not providing height for image view and telling to expand according to aspect ratio of image. set a max height for image view. to better UX centre imageview in both axis and a fixed either height or width and a maximum for other width or height.
img_card_type.centerXAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerXAnchor, multiplier: 1).isActive = true
img_card_type.centerYAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerYAnchor, multiplier: 1).isActive = true
img_card_type.widthAnchor.constraint(equalToConstant: 100).isActive = true
img_card_type.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true

Why adding frame doesn't show a UIView when initializing with lazy var in Swift

I'm working on a Swift project and there is one thing I'm not clear about making UIs programmatically.
I tried to display a simple UIView on the screen.
lazy var container: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
view.backgroundColor = UIColor.systemImageGray()
view.layer.cornerRadius = view.layer.bounds.width / 2
view.clipsToBounds = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
setupConstraints()
}
func setupConstraints() {
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
}
The code above works fine, but since I set the with and height twice, I feel it's redundant, like UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and set the width and height constraints in setupConstraints.
Since I set the width and height in UIView's frame, I thought I don't need to set the width and height constraints in the setupConstraints, but it doesn't show the view unless I add the width and height constraints again. So in this case, why I cannot set the width and height in UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and I also have to add the width/height constraints again?
frame is useful when you are not using the Autolayout engine to place your views.
When you do:
container.translatesAutoresizingMaskIntoConstraints = false
You are explicitly telling the engine to ignore the frame & that you are responsible for applying a new set of constraints.
And hence you eventually do:
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
Which sets the positioning & dynamic sizing as per Autolayout's expectations.
translatesAutoresizingMaskIntoConstraints
A Boolean value that determines whether the view’s autoresizing mask
is translated into Auto Layout constraints.
Ref: https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco

My textView bottomAnchor does not seem to work?

I have a textView and I have a line, I set the line's frame without contraints and set textView frame with constraints. Simply what I want is the textView to follow the line, so I put a bottomAnchor to textView equal to the topAnchor of the line. Yet when I animate the line the textView does not follow? What am I doing wrong?
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.transform = CGAffineTransform.identity.translatedBy(x: 0, y: 30)
}) { (true) in
self.view.layoutIfNeeded()
}
}
As #Vollan correctly said animating transform property is not the best option. Here is quote from Apple documentation: "In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame." Therefore animation of transform property doesn't change layout of textView. I recommend you to animate frame property instead of transform.
However, if you switch to frame animation it doesn't fix all your problems. If you keep your animation inside viewDidLoad method you may encounter very strange behavior. The reason is that in viewDidLoad the view itself is not yet laid out properly. Starting animation inside viewDidLoad may lead to unpredicted results.
At last you need adjust your animation block. Apple recommends to apply layoutIfNeeded inside the animation block. Or at least they used to recommend it then autolayout was introduced - watch this WWDC video (starting from 30th minute) for further details.
If you apply all recommendations above your code should look like this:
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
var triggeredAnimation = false
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// viewDidAppear may be called several times during view controller lifecycle
// triggeredAnimation ensures that animation will be called just once
if self.triggeredAnimation {
return
}
self.triggeredAnimation = true
let oldFrame = self.testLine.frame
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.frame = CGRect(x: oldFrame.minX, y: oldFrame.minY + 30, width: oldFrame.width,
height: oldFrame.height)
self.view.layoutIfNeeded()
})
}
Anchor points make references to others positions, meaning. It is still referensed to y = 355 as you transform it and not actually "move" it.
What i recommend is that you don't mix using frame-based layout and anchorpoints / layout constraints.

In Swift, programmatically creating UIView and adding controls to it and using auto layout, causes the controls to appear on the view's parent

I am trying to write a simple composite component for iOS in Swift 3. It consists of a UILabel followed by an UITextField laid out horizontally followed by a line under them. But What happens is the UILabel disappears, UITextField appears on the parent view and line also disappears.
My design in sketch
What it actually looks like in the Storyboard
My component's constraints in the view controller
My intention was to use Auto Layout, anchor the label to top and leading anchors of the view, anchor the textfield to top of the view and trailing anchor of the label with a constant, so they would appear side by side.
I did do a lot of research on this, one site that looked pretty close to what I wanted was https://www.raywenderlich.com/125718/coding-auto-layout, and I think I am following more or less the same approach.
I am doing something obviously wrong, but can't figure out what. Any help is much appreciated, I have been at this for a few days now.
import UIKit
#IBDesignable
class OTextEdit: UIView {
#IBInspectable var LabelText: String = "Label"
#IBInspectable var SecureText: Bool = false
#IBInspectable var Color: UIColor = UIColor.black
#IBInspectable var Text: String = "" {
didSet {
edit.text = Text
}
}
fileprivate let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 35))
fileprivate let edit = UITextField(frame: CGRect(x: 210, y: 0, width: 200, height: 35))
fileprivate let line: UIView = UIView()
override var intrinsicContentSize: CGSize {
return CGSize(width: 300, height: 100)
}
func setup() {
label.text = LabelText
label.textColor = Color
label.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.borderStyle = .roundedRect
edit.isSecureTextEntry = SecureText
line.backgroundColor = UIColor.white
self.addSubview(label)
self.addSubview(edit)
self.addSubview(line)
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
setup()
setupConstaints()
}
func setupConstaints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -10).isActive = true
label.topAnchor.constraint(equalTo: topAnchor)
edit.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.topAnchor.constraint(equalTo: self.topAnchor)
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
}
}
You haven't got a series of constraints top to bottom, so auto layout can't determine the content size of your object. You have tried to set this via the initrinsicContentSize but you shouldn't need to do this.
You also need to set a horizontal hugging priority for your label to let auto layout know that you want the text field to expand:
I removed your override of intrinsicContentSize and changed your constraints to:
Constrain the bottom of the label to the top of the line
Constrain the bottom of the line to the bottom of the superview
Constrain the baseline of the label to the baseline of the text field
Remove the constraint between the top of the text field and the superview
Set the horizontal hugging priority of the label.
func setupConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.topAnchor.constraint(equalTo: topAnchor)
label.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -8).isActive = true
edit.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
I think it is pretty close to what you are after.

Make a View stick to bottom with UITableViewController

I want to make a UIView stick on the bottom while I am scrolling in my UITableView.
My idea was to set a UIView with the position of right above the navigation item. Set it's zPosition to 1.
The problem is, that the yPosition of my UITableView varies.
Any idea how to solve this?
Edit:
Providing Screenshots for visible vs. expected behaviour:
Visible:
This is when I scroll:
Expected:
As seen on Tinder Camera Symbol above Table:
Edit2:
This code is what I use to put the rectangle to the bottom.
It works until I swipe the UITableView - The rectangle also scrolls up.
let bounds = self.view.bounds
let yPosition = self.navigationController?.toolbar.frame.minY
print(yPosition)
let myView = UIView(frame: CGRect(x: 0, y: yPosition! - bounds.height/6, width: bounds.width, height: bounds.height/6))
myView.backgroundColor = myColor.rookie
myView.alpha = 0.8
myView.layer.zPosition = 1
myView.isUserInteractionEnabled = true
self.view.addSubview(myView)
there is a solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}

Resources