I am trying to create a button using programmed constraints. I am trying to not use the storyboard. I am having trouble unwrapping the button. How do I do this?
import UIKit
var aa: [NSLayoutConstraint] = []
class ViewController: UIViewController {
var btn: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn)
let leadingc2 = btn.widthAnchor.constraint(equalToConstant: 80)
let trailingC2 = btn.heightAnchor.constraint(equalToConstant: 50)
let topc2 = btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: -50)
let bottomc2 = btn.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -250)
aa = [leadingc2,trailingC2,topc2,bottomc2]
NSLayoutConstraint.activate(aa)
}
}
You do not need to unwrap it. You need to instantiate it before using it.
override func viewDidLoad() {
super.viewDidLoad()
btn = UITextField() // Create the button like this before using it.
self.view.addSubview(btn)
btn.translatesAutoresizingMaskIntoConstraints = false
let leadingc2 = btn.widthAnchor.constraint(equalToConstant: 80)
let trailingC2 = btn.heightAnchor.constraint(equalToConstant: 50)
let topc2 = btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: -50)
let bottomc2 = btn.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -250)
aa = [leadingc2,trailingC2,topc2,bottomc2]
NSLayoutConstraint.activate(aa)
}
Any variable declared with ! will be force unwrapped, meaning that if you forget to create an instance and use the variable, it will throw an error and crash your app.
Use this code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
setupConstraints()
}
lazy var button: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Button", for: .normal)
button.backgroundColor = .blue
//your action here
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
private func setupConstraints() {
button.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 100)
let left = NSLayoutConstraint(item: button, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 50)
let right = NSLayoutConstraint(item: button, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: -50)
let height = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
view.addConstraints([top, left, right, height])
}
#objc func buttonAction() {
print("Button has pressed")
}
}
Related
I have a UITextField that is in a specific view with constraints. It works flawlessly if the text is not a single word. But as you can see it in the above image, if the text is only one word, it does not adjust the font size to fit. How can I fix it? Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
lazy var textInput : UITextField = {
let textInput = UITextField()
textInput.textAlignment = .center
textInput.translatesAutoresizingMaskIntoConstraints = false
textInput.minimumFontSize = 10;
textInput.adjustsFontSizeToFitWidth = true;
textInput.backgroundColor = .yellow
myView.addSubview(textInput)
var lConst = NSLayoutConstraint(item: textInput, attribute: .leading, relatedBy: .equal, toItem: myView, attribute: .leading, multiplier: 1, constant: 0)
var toConst = NSLayoutConstraint(item: textInput, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1, constant: 0)
var trConst = NSLayoutConstraint(item: textInput, attribute: .trailing, relatedBy: .equal, toItem: myView, attribute: .trailing, multiplier: 1, constant: 0)
var bConst = NSLayoutConstraint(item: textInput, attribute: .bottom, relatedBy: .equal, toItem: myView, attribute: .bottom, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([lConst,toConst,trConst,bConst])
return textInput
} ()
override func viewDidLoad() {
super.viewDidLoad()
_ = textInput
textInput.font = UIFont.boldSystemFont(ofSize: 200)
// Do any additional setup after loading the view.
}
}
If you mean "character" instead of "word" (looking to your images), I think your problem is, that by default the first character is uppercase. If you want to start your text with lowercase character you have to set this:
textInput.autocapitalizationType = .none
Edit: Sorry I just misinterpreted your images and don't saw, that your letter is cut at top and bottom.
I wrote your code with a little bit different layout syntax and it works fine, even when the view is smaller than your font size.
class ViewController: UIViewController {
lazy var myView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var textInput : UITextField = {
let textInput = UITextField()
textInput.textAlignment = .center
textInput.translatesAutoresizingMaskIntoConstraints = false
textInput.minimumFontSize = 10;
textInput.adjustsFontSizeToFitWidth = true;
textInput.backgroundColor = .yellow
return textInput
} ()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
// Do any additional setup after loading the view.
}
func setupLayout(){
view.addSubview(myView)
NSLayoutConstraint.activate([
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
myView.widthAnchor.constraint(equalToConstant: 180),
myView.heightAnchor.constraint(equalToConstant: 180)
])
textInput.font = UIFont.boldSystemFont(ofSize: 200)
myView.addSubview(textInput)
NSLayoutConstraint.activate([
textInput.topAnchor.constraint(equalTo:myView.topAnchor),
textInput.trailingAnchor.constraint(equalTo: myView.trailingAnchor),
textInput.bottomAnchor.constraint(equalTo: myView.bottomAnchor),
textInput.leadingAnchor.constraint(equalTo: myView.leadingAnchor)
])
}
}
I am trying to create a login in page that is supposed to look like the one listed below. The code I have written should produce this view minus the logo and minus [Login|Register] toggle button. The height and width of my box is also different, but I'm not concerned about that.
Currently I am getting this output. I'm concerned about how the words are all overlapping each other at the top.
In the code I listed below I create my 3 text fields, my button and the container for my text fields. I believe there is something wrong in the function fieldConstraints. In this function I look through an array of all my text fields and assign the neccessary constraints to them. They all get the same constraints except that the topAnchor of each text field after the first one is set equal to the bottomAnchor of the separator that was placed underneath the text field before. Those blue lines in between the text fields are the separators.
Main class
class SignIn: UIViewController {
override func loadView() {
super.loadView()
let inputContainer = inputDataContainer()
constraintsToCenterSubview(forView: inputContainer, width: 100, height: 100)
let nameField = field(for: "Name")
let emailField = field(for: "Email address")
let passField = field(for: "Password")
let fields = [nameField, emailField, passField]
let button = loginButton()
fieldConstraints(subviews: fields, superview: inputContainer)
self.centerViewBelow(forView: button, whichIsBelow: inputContainer, increaseWidthBy: 0)
}
func inputDataContainer() -> UIView{
let inputView = UIView(frame: CGRect(x: self.view.center.x, y: self.view.center.y, width: CGFloat(100), height: CGFloat(100)))
inputView.backgroundColor = UIColor.white
inputView.translatesAutoresizingMaskIntoConstraints = false
inputView.layer.cornerRadius = 5
inputView.layer.masksToBounds = true
self.view.addSubview(inputView)
//inputView = centerViewBelow(forView: inputView, whichIsBelow: self.view, increaseWidthBy: 100)
return inputView
}
func loginButton() -> UIButton {
let button = UIButton()
button.backgroundColor = UIColor(r: 80, g: 101, b: 161)
button.setTitle("Submit", for: [])
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(UIColor.white, for: [])
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
self.view.addSubview(button)
return button
}
func field(for name: String) -> UITextField{
let tf = UITextField()
tf.placeholder = name
tf.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(tf)
return tf
}
func fieldSep() -> UIView {
let view = UIView()
view.backgroundColor = UIColor(r: 220, g: 220, b: 220)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}
func fieldConstraints(subviews: [UIView], superview: UIView) {
var sep: UIView?
let len = subviews.endIndex
for (idx, subview) in subviews.enumerated(){
superview.addSubview(subview)
subview.leftAnchor.constraint(equalTo: superview.leftAnchor)
subview.rightAnchor.constraint(equalTo: superview.rightAnchor)
subview.widthAnchor.constraint(equalTo: superview.widthAnchor)
subview.heightAnchor.constraint(equalTo: superview.heightAnchor, multiplier: CGFloat(1/len))
if (sep != nil){
subview.topAnchor.constraint(equalTo: sep!.bottomAnchor)
}else{
subview.topAnchor.constraint(equalTo: superview.topAnchor)
}
sep = fieldSep()
if idx < subviews.endIndex-1 {
self.view.addSubview(sep!)
sep?.leftAnchor.constraint(equalTo: superview.leftAnchor)
sep?.rightAnchor.constraint(equalTo: superview.rightAnchor)
sep?.topAnchor.constraint(equalTo: subview.bottomAnchor)
}
}
}
}
Extensions
extension UIColor {
convenience init(r:CGFloat, g:CGFloat, b: CGFloat) {
self.init(red: r/255, green: g/255, blue: b/255, alpha: 1)
}
}
extension UIViewController {
func centerViewBelow(forView view: UIView, whichIsBelow topView: UIView, increaseWidthBy constant: CGFloat){
let topConstraint = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: topView, attribute: .bottom, multiplier: 1, constant: 20)
let widthConstraint = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: topView, attribute: .width, multiplier: 1, constant: constant)
let centerConstraint = NSLayoutConstraint(item: view, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 50)
NSLayoutConstraint.activate([topConstraint, widthConstraint, centerConstraint, heightConstraint])
//return view
}
func constraintsToCenterSubview(forView view: UIView, width: Int, height: Int){
let centerXConstraint = NSLayoutConstraint(item: view, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let centerYConstraint = NSLayoutConstraint(item: view, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: CGFloat(width))
let heightConstraint = NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: CGFloat(height))
NSLayoutConstraint.activate([centerXConstraint, centerYConstraint, widthConstraint, heightConstraint])
}
}
Thank you
----------------------------------------------------------------------------------------------------------
Update
So I was pretty much able to do it by changing my container into a stack view. But this made it so that my corners were no longer rounded. Does anyone know how to fix this?
func inputDataContainer() -> UIStackView{
let inputView = UIStackView(frame: CGRect(x: self.view.center.x, y: self.view.center.y, width: CGFloat(100), height: CGFloat(100)))
inputView.backgroundColor = UIColor.white
inputView.translatesAutoresizingMaskIntoConstraints = false
inputView.layer.cornerRadius = 5
inputView.layer.masksToBounds = true
inputView.distribution = .fillEqually
inputView.axis = .vertical
inputView.spacing = 1
self.view.addSubview(inputView)
//inputView = centerViewBelow(forView: inputView, whichIsBelow: self.view, increaseWidthBy: 100)
return inputView
}
func fieldConstraints(subviews: [UIView], superview: UIStackView) {
for subview in subviews{
superview.addArrangedSubview(subview)
subview.clipsToBounds = true
}
}
Example Screen Shot of Current App
Try to give height for
sep?.heightAnchor.constraint(equalToConstant:1.0).isActive = true
also for all constraints in fieldConstraints you forget
.isActive = true
or use NSLayoutConstraint.activate , like
NSLayoutConstraint.activate([
subview.leftAnchor.constraint(equalTo: superview.leftAnchor),
subview.rightAnchor.constraint(equalTo: superview.rightAnchor)
subview.widthAnchor.constraint(equalTo: superview.widthAnchor),
subview.heightAnchor.constraint(equalTo: superview.heightAnchor, multiplier: CGFloat(1/len))
])
This approach will work but it will be better to use a vertical stackview with distribution .fillEqually and it'll will partition them and add like
fileds.forEach { stackview.addArrangedSubview($0) }
use UIStackView. it will help you save time to build a complicated UI.
You will have a little trouble using a UIStackView. You could embed it in a UIView and give that view the rounded corners, but then you have to add padding to the fields within the stack view in order to get your separator lines.
Here is another approach that might work better for you. It's closer to your original code, adding the fields and separator views to a UIView:
extension UIColor {
convenience init(r:CGFloat, g:CGFloat, b: CGFloat) {
self.init(red: r/255, green: g/255, blue: b/255, alpha: 1)
}
}
class SampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(r: 65, g: 92, b: 144)
let nameField = field(for: "Name")
let sep1 = fieldSep()
let emailField = field(for: "Email address")
let sep2 = fieldSep()
let passField = field(for: "Password")
let views = [nameField, sep1, emailField, sep2, passField]
let inputView = inputDataContainer(with: views)
view.addSubview(inputView)
NSLayoutConstraint.activate([
inputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
inputView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
inputView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
])
}
func field(for s: String) -> UITextField {
let f = UITextField()
f.translatesAutoresizingMaskIntoConstraints = false
f.placeholder = s
f.borderStyle = .none
return f
}
func fieldSep() -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
v.backgroundColor = UIColor(r: 220, g: 220, b: 220)
return v
}
func inputDataContainer(with subviews: [UIView]) -> UIView {
let horizontalPadding: CGFloat = 8.0
let verticalSpacing: CGFloat = 8.0
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.backgroundColor = .white
containerView.layer.cornerRadius = 5
var previousView: UIView?
for subview in subviews{
containerView.addSubview(subview)
// if it's a text field, we want padding on left and right
if subview is UITextField {
subview.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: horizontalPadding).isActive = true
subview.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -horizontalPadding).isActive = true
} else {
subview.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0).isActive = true
subview.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0).isActive = true
}
if subview == subviews.first {
// if it's the first subview, constrain to top of container
subview.topAnchor.constraint(equalTo: containerView.topAnchor, constant: verticalSpacing).isActive = true
} else {
// unwrap previousView and constrain subview to bottom of previous subview
if let pv = previousView {
subview.topAnchor.constraint(equalTo: pv.bottomAnchor, constant: verticalSpacing).isActive = true
}
}
if subview == subviews.last {
// if it's the last subview, constrain to bottom of container
subview.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -verticalSpacing).isActive = true
}
// save reference to current subview
previousView = subview
}
return containerView
}
}
And the result:
I need to delete the background of the search bar.
The part where text is entered (white color) make it higher.
put a green border around the white part.
customize the font.
I need this:
What I have achieved is this:
My code:
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
self.searchBar.layer.borderColor = #colorLiteral(red: 0.2352941176, green: 0.7254901961, blue: 0.3921568627, alpha: 1)
self.searchBar.layer.borderWidth = 1
self.searchBar.clipsToBounds = true
self.searchBar.layer.cornerRadius = 20
}
Try using this code:
class JSSearchView : UIView {
var searchBar : UISearchBar!
override func awakeFromNib()
{
// the actual search barw
self.searchBar = UISearchBar(frame: self.frame)
self.searchBar.clipsToBounds = true
// the smaller the number in relation to the view, the more subtle
// the rounding -- https://www.hackingwithswift.com/example-code/calayer/how-to-round-the-corners-of-a-uiview
self.searchBar.layer.cornerRadius = 5
self.addSubview(self.searchBar)
self.searchBar.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20)
let trailingConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20)
let yConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraints([yConstraint, leadingConstraint, trailingConstraint])
self.searchBar.backgroundColor = UIColor.clear
self.searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
self.searchBar.tintColor = UIColor.clear
self.searchBar.isTranslucent = true
// https://stackoverflow.com/questions/21191801/how-to-add-a-1-pixel-gray-border-around-a-uisearchbar-textfield/21192270
for s in self.searchBar.subviews[0].subviews {
if s is UITextField {
s.layer.borderWidth = 1.0
s.layer.cornerRadius = 10
s.layer.borderColor = UIColor.green.cgColor
}
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// the half height green background you wanted...
let topRect = CGRect(origin: .zero, size: CGSize(width: self.frame.size.width, height: (self.frame.height / 2)))
UIColor.green.set()
UIRectFill(topRect)
}
}
And to use it, drop a custom view into your xib or storyboard file and simply set the custom class type to the name of the class (JSSearchView).
I'm trying to use inputAccessoryViewController in my app, but faced a problem with changing height of accessory view. I tried to change frame/bounds of the view, I also tried to handle height of the accessory view using constraints. But nothing worked well.
InputViewController code:
import UIKit
import RxSwift
import RxCocoa
class InputViewController: UIInputViewController {
private var separatorView: UIView?
private var answerTextView: ConstrainedTextView?
private var closeButton: UIButton?
private var tipLabel: UILabel?
// private var generalHeightConstraint: NSLayoutConstraint?
private var separatorHeightConstraint: NSLayoutConstraint?
private var answerTextViewBottomConstraint: NSLayoutConstraint?
private let junk = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
private func configureView() {
// view.autoresizingMask = .flexibleHeight
view.backgroundColor = UIColor.white
view.frame = CGRect(x: 0, y: 0, width: view.superview?.bounds.width ?? 100, height: 70)
// view.translatesAutoresizingMaskIntoConstraints = false
// generalHeightConstraint = AutoLayoutSetAttribute(view, .height, 70)
// Separator
separatorView = UIView()
separatorView?.backgroundColor = UIColor.gray
separatorView?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(separatorView!)
AutoLayoutEqualizeSuper(separatorView, .left, 0)
AutoLayoutEqualizeSuper(separatorView, .right, 0)
AutoLayoutEqualizeSuper(separatorView, .top, 0)
separatorHeightConstraint = AutoLayoutSetAttribute(separatorView, .height, 1)
// Close Button
closeButton = UIButton(type: .system)
closeButton?.setTitle("Hide", for: .normal)
closeButton?.titleLabel?.font = UIFont.systemFont(ofSize: 17)
closeButton?.translatesAutoresizingMaskIntoConstraints = false
closeButton?.addTarget(self, action: #selector(dismissKeyboard), for: .touchUpInside)
view.addSubview(closeButton!)
AutoLayoutSetAttribute(closeButton, .width, 70)
AutoLayoutEqualizeSuper(closeButton, .right, -5)
view.addConstraint(NSLayoutConstraint(item: closeButton!, attribute: .top, relatedBy: .equal, toItem: separatorView, attribute: .bottom, multiplier: 1, constant: 0))
// Tip Label
tipLabel = UILabel()
tipLabel?.textColor = UIColor.darkGray
tipLabel?.text = "Input answer:"
tipLabel?.font = UIFont.systemFont(ofSize: 17)
tipLabel?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tipLabel!)
AutoLayoutEqualizeSuper(tipLabel, .left, 5)
AutoLayoutEqualize(tipLabel, separatorView, .top, 0)
view.addConstraint(NSLayoutConstraint(item: tipLabel!, attribute: .right, relatedBy: .equal, toItem: closeButton, attribute: .left, multiplier: 1, constant: 0))
// Text View
answerTextView = ConstrainedTextView()
answerTextView?.backgroundColor = UIColor.white
answerTextView?.delegate = self
answerTextView?.scrollsToTop = false
answerTextView?.showsVerticalScrollIndicator = false
answerTextView?.font = REG_FONT(15)
answerTextView?.maxLines = 5
answerTextView?.translatesAutoresizingMaskIntoConstraints = false
answerTextView?.layer.masksToBounds = true
answerTextView?.layer.cornerRadius = 7
answerTextView?.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor
answerTextView?.layer.borderWidth = 1
view.addSubview(answerTextView!)
AutoLayoutEqualizeSuper(answerTextView, .left, 5)
AutoLayoutEqualizeSuper(answerTextView, .right, -5)
answerTextViewBottomConstraint = AutoLayoutEqualizeSuper(answerTextView, .bottom, -5)
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: tipLabel, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: closeButton, attribute: .bottom, multiplier: 1, constant: 0))
answerTextView?
.rx
.observe(CGRect.self, "bounds")
.distinctUntilChanged {
$0?.size.height == $1?.size.height
}
.subscribe { [unowned self] newBounds in
if var newHeight = newBounds.element??.size.height,
let separatorHeight = self.separatorHeightConstraint?.constant,
let buttonHeight = self.closeButton?.intrinsicContentSize.height,
let bottomSpace = self.answerTextViewBottomConstraint?.constant {
newHeight = newHeight == 0 ? 30 : newHeight
let generalHeight = newHeight + separatorHeight + buttonHeight + abs(bottomSpace)
self.view.frame = CGRect(x: 0, y: 0, width: self.view.superview?.bounds.width ?? 100, height: generalHeight)
// self.generalHeightConstraint?.constant = generalHeight
// UIView.animate(withDuration: 0.2) {
// self.view.setNeedsLayout()
// self.view.layoutIfNeeded()
// }
}
}
.addDisposableTo(junk)
}
}
// MARK: - UITextViewDelegate Protocol Conformance
extension InputViewController: UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = view
return true
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = nil
return true
}
}
View Controller where input accessory VC is used:
import UIKit
class TestViewController: UIViewController {
override var inputAccessoryViewController: UIInputViewController? {
return SDAnswerInputViewController()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Can you explain how shall I correctly modify height of input accessory view overriding inputAccessoryViewController?
The problem was in these two lines:
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: tipLabel, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: closeButton, attribute: .bottom, multiplier: 1, constant: 0))
The answerTextView couldn't modify it's height because of constraints at the bottom and the top.
I'm working with a iMessage application and have programmatically added a view. However I can't seem to work out the correct constraints for making it the correct size at all times. For example, the view moves down a few hundred px if I leave the extension for another and come back to it. I think this has something to do with the .isActive. My goal is to make the view automatically resize to always be the right size or take up the full available height and width.
func createBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .small)
addChildViewController(controller)
view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.stickerBrowserView.backgroundColor = UIColor.blue
controller.stickerBrowserView.dataSource = self
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
view.centerXAnchor.constraint(equalTo: controller.view.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor).isActive = true
}
Screenshot: https://d17oy1vhnax1f7.cloudfront.net/items/1F2B0s3v0s1k3E2L0Z07/Screen%20Shot%202016-09-19%20at%2011.42.51%20AM.png
to better explain things I've put together the following. This demonstrates two methods of fixing the layout for subviews. When using constraints, I prefer to create the constraints as an array and activate them all in one go, as you will see in the code for createredSquareWithConstraints. A constraint is simply a linear equation relating the features of one view to that of another. In "pseudocode", for example, the first constraint in my array could be written:
"Set the leading margin of the subview equal to 1 times the leading margin of the container view plus a constant of 0."
(This is why I was getting confused earlier as it looked to me as though you were setting the containing view's constraints based on the characteristics of one of its subviews.)
While it remains perfectly valid to use layout constraints, I think the preferred methodology these days is to override the viewWillTransitionToSize() delegate method, which simply asks you to specify, given a size for the containing view, what the frame of a view controller's subviews should be. As such, I've included an implementation of this too, creating a yellow square with an initial frame that is then modified whenever viewWillTransitionToSize is called. I personally find this a lot less fiddly that using layout constraints.
If you lay around with the buttons and rotate the screen you should see that either method achieves the same thing. [NB I have labelled one square as constrained and one as unconstrained, but in reality they are of course both constrained, just in different ways. I would add that this is clearly not how you would do things in practice - you should choose one methodology and stick to it otherwise your code will be all over the place!].
Hope that helps!
import UIKit
class ViewController: UIViewController {
var constrainedredSquare : UIView!
var unconstrainedRedSquare : UIView!
var methodOneButton : UIButton!
var methodTwoButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.blue
func getButton(name: String) -> UIButton {
let button : UIButton = UIButton()
button.backgroundColor = UIColor.white
button.layer.cornerRadius = 3
button.clipsToBounds = true
button.setTitle(name, for: UIControlState.normal)
button.setTitleColor(UIColor.black, for: UIControlState.normal)
return button
}
self.methodOneButton = getButton(name: "Red - Constraints")
self.methodTwoButton = getButton(name: "Yellow - viewWillTransitionToSize")
self.methodOneButton.addTarget(self, action: #selector(self.createRedSquareWithConstraints), for: .touchUpInside)
self.methodTwoButton.addTarget(self, action: #selector(self.createYellowSquareWithoutConstraints), for: .touchUpInside)
self.methodOneButton.frame = CGRect(origin: CGPoint(x: 200, y: 100), size: CGSize(width: 300, height: 300))
self.methodTwoButton.frame = CGRect(origin: CGPoint(x: self.view.frame.width - 500, y: 100), size: CGSize(width: 300, height: 300))
self.view.addSubview(self.methodOneButton)
self.view.addSubview(self.methodTwoButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.frame = CGRect(origin: CGPoint.zero, size: size)
}
self.methodOneButton.frame = CGRect(origin: CGPoint(x: 200, y: 100), size: CGSize(width: 300, height: 300))
self.methodTwoButton.frame = CGRect(origin: CGPoint(x: size.width - 500, y: 100), size: CGSize(width: 300, height: 300))
}
func createYellowSquareWithoutConstraints() {
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.removeFromSuperview()
}
else
{
if let _ = constrainedredSquare {
self.constrainedredSquare.removeFromSuperview()
}
self.unconstrainedRedSquare = UIView()
self.unconstrainedRedSquare.backgroundColor = UIColor.yellow
self.unconstrainedRedSquare.frame = CGRect(origin: CGPoint.zero, size: self.view.frame.size)
self.view.addSubview(self.unconstrainedRedSquare)
self.view.bringSubview(toFront: self.methodOneButton)
self.view.bringSubview(toFront: self.methodTwoButton)
}
}
func createRedSquareWithConstraints() {
if let _ = self.constrainedredSquare {
self.constrainedredSquare.removeFromSuperview()
}
else
{
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.removeFromSuperview()
}
let redSquare : UIView = UIView()
redSquare.backgroundColor = UIColor.red
self.view.addSubview(redSquare)
self.view.bringSubview(toFront: self.methodOneButton)
self.view.bringSubview(toFront: self.methodTwoButton)
let rsConstraints : [NSLayoutConstraint] = [NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.trailing, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0)]
redSquare.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(rsConstraints)
}
}
}
You can use my extension to UIView. It allows to add extra padding on any side (only if you want to):
public extension UIView {
typealias ConstraintsTupleStretched = (top:NSLayoutConstraint, bottom:NSLayoutConstraint, leading:NSLayoutConstraint, trailing:NSLayoutConstraint)
func addSubviewStretched(subview:UIView?, insets: UIEdgeInsets = UIEdgeInsets() ) -> ConstraintsTupleStretched? {
guard let subview = subview else {
return nil
}
subview.translatesAutoresizingMaskIntoConstraints = false
addSubview(subview)
let constraintLeading = NSLayoutConstraint(item: subview,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: insets.left)
addConstraint(constraintLeading)
let constraintTrailing = NSLayoutConstraint(item: self,
attribute: .Right,
relatedBy: .Equal,
toItem: subview,
attribute: .Right,
multiplier: 1,
constant: insets.right)
addConstraint(constraintTrailing)
let constraintTop = NSLayoutConstraint(item: subview,
attribute: .Top,
relatedBy: .Equal,
toItem: self,
attribute: .Top,
multiplier: 1,
constant: insets.top)
addConstraint(constraintTop)
let constraintBottom = NSLayoutConstraint(item: self,
attribute: .Bottom,
relatedBy: .Equal,
toItem: subview,
attribute: .Bottom,
multiplier: 1,
constant: insets.bottom)
addConstraint(constraintBottom)
return (constraintTop, constraintBottom, constraintLeading, constraintTrailing)
}
}
Usage:
view.addSubviewStretched(tableView)
let BorderedBackgroundInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
view?.addSubviewStretched(calendar.view, insets: BorderedBackgroundInset)