Swift: How to programmatically create reusable views? - ios

Let's say I am describing my own UIView, let's call it a HeaderView. I want the HeaderView to have the exact same properties, but only differ in label text. Here is how I currently have it:
private let headerView: UIView = {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let view = UIView()
view.backgroundColor = .white
view.heightAnchor.constraint(equalToConstant: 65).isActive = true
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.textAlignment = .left
label.textColor = .black
label.text = "Search"
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 5).isActive = true
return view
}()
How I'd use it:
view.addSubview(headerView)
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
What if I want 3 of these header views with varying text? How would I make it into a reusable programmatic view?

You can create a subclass of UIView and reuse it anywhere
class HeaderView: UIView {
let innerview = UIView()
let innerlabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
sharedLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedLayout()
}
private func sharedLayout() {
self.addSubview(innerview)
self.innerView.backgroundColor = UIColor.red
innerview.translatesAutoresizingMaskIntoConstraints = false
innerview.trailingAnchor.constraint(equalTo:self.trailingAnchor).isActive = true
innerview.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
innerview.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
innerview.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// configure other items here
}
}

Instead of a variable you could have a function:
func buildHeaderView(withText text: String) -> UIView {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let view = UIView()
view.backgroundColor = .white
view.heightAnchor.constraint(equalToConstant: 65).isActive = true
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.textAlignment = .left
label.textColor = .black
label.text = text
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 5).isActive = true
return view
}
Now you could use this function like:
let searchHeaderView = buildHeaderView(withText: "Search")
view.addSubview(searchHeaderView)
let otherView = buildHeaderView(withText: "Other")

Related

How to add UIView Gesture - Programmatically UIView

I have searched a lot about adding a gesture on UIView when it is tapped. There are many solutions but none of them worked for me.
I have made UIView class programmatically and using the view in different classes.
Here is my class of UIView:
class PaymentServiceView: UIView {
private let views: UIView = {
let view = UIView()
view.backgroundColor = .white
// view.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
view.layer.borderColor = UIColor.gray.cgColor
view.layer.shadowOpacity = 0.3
view.layer.shadowColor = UIColor.gray.cgColor
view.layer.shadowRadius = 10
view.layer.borderWidth = 0.1
view.layer.cornerRadius = 20
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let titleLbl: UILabel = {
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.black
label.font = UIFont(name: AppFontName.circularStdRegular, size: 18)
label.clipsToBounds = true
return label
}()
private let subLbl: UILabel = {
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.gray
label.numberOfLines = 0
label.textAlignment = .left
label.font = UIFont(name: AppFontName.robotoRegular, size: 15)
label.clipsToBounds = true
return label
}()
private let image: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let btnImage: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.image = UIImage(named: IconName.chevron_down)?.transform(withNewColor: UIColor.btnGray)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
init(titleLabel: String, subTitleLabel: String, imageName: String) {
super.init(frame: CGRect.zero)
self.addSubview(views)
self.views.addSubview(btnImage)
self.views.addSubview(titleLbl)
self.views.addSubview(image)
self.views.addSubview(subLbl)
titleLbl.text = titleLabel
image.image = UIImage(named: imageName)
subLbl.text = subTitleLabel
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupConstraints() {
self.views.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.views.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.views.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.views.widthAnchor.constraint(equalToConstant: 320).isActive = true
self.image.centerYAnchor.constraint(equalTo: self.views.centerYAnchor).isActive = true
self.image.leadingAnchor.constraint(equalTo: self.views.leadingAnchor, constant: 15).isActive = true
self.image.widthAnchor.constraint(equalToConstant: 30).isActive = true
self.image.heightAnchor.constraint(equalToConstant: 30).isActive = true
self.titleLbl.centerYAnchor.constraint(equalTo: self.views.centerYAnchor, constant: -35).isActive = true
self.titleLbl.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 15).isActive = true
self.subLbl.topAnchor.constraint(equalTo: titleLbl.bottomAnchor, constant: 5).isActive = true
self.subLbl.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 15).isActive = true
self.subLbl.trailingAnchor.constraint(equalTo: btnImage.leadingAnchor, constant: -15).isActive = true
btnImage.topAnchor.constraint(equalTo: views.topAnchor, constant: 55).isActive = true
btnImage.rightAnchor.constraint(equalTo: views.rightAnchor, constant: -10).isActive = true
btnImage.heightAnchor.constraint(equalToConstant: 10).isActive = true
}
}
Now Im using this UIView class in PaymentServicesViewController
class PaymentServicesViewController: UIViewController {
private (set) lazy var headerView: HeaderView = { [unowned self] in
let view = HeaderView.init(titleLbl: Headings.paymentService, closeAction: {
self.navigationController?.popViewController(animated: true)
}, nextAction: {
print("next")
}, previousAction: {
self.navigationController?.popViewController(animated: true)
})
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let fundTransferView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.fundTransfer, subTitleLabel: Description.fundTransferDecription , imageName: IconName.fundImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let subscriptionView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.subscribe, subTitleLabel: Description.subscriptionDescription, imageName: IconName.subImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let billPaymentView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.billPayment, subTitleLabel: Description.billPaymentDescription , imageName: IconName.billImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray2
let fundtrasnferGesture = UITapGestureRecognizer(target: self, action: #selector(fundTranferTapped))
fundTransferView.isUserInteractionEnabled = true
self.fundTransferView.addGestureRecognizer(fundtrasnferGesture)
let subscribeGesture = UITapGestureRecognizer(target: self, action: #selector(subscribeTapped))
subscriptionView.isUserInteractionEnabled = true
self.subscriptionView.addGestureRecognizer(subscribeGesture)
let billPaymentGesture = UITapGestureRecognizer(target: self, action: #selector(billPaymentTapped))
fundTransferView.isUserInteractionEnabled = true
self.billPaymentView.addGestureRecognizer(billPaymentGesture)
view.addSubview(headerView)
view.addSubview(subscriptionView)
view.addSubview(fundTransferView)
view.addSubview(billPaymentView)
setupConstraint()
}
#objc func fundTranferTapped(sender: UITapGestureRecognizer) {
print("FundTransfer Tapped.")
}
#objc func subscribeTapped(sender: UITapGestureRecognizer) {
print("Subscribe Tapped.")
}
#objc func billPaymentTapped(sender: UITapGestureRecognizer) {
print("BillPayment Tapped.")
}
private func setupConstraint() {
headerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
subscriptionView.trailingAnchor.constraint(equalTo: fundTransferView.trailingAnchor, constant: 0).isActive = true
subscriptionView.leadingAnchor.constraint(equalTo: fundTransferView.leadingAnchor, constant: 0).isActive = true
subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: -130).isActive = true
fundTransferView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
fundTransferView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
billPaymentView.leadingAnchor.constraint(equalTo: fundTransferView.leadingAnchor, constant: 0).isActive = true
billPaymentView.trailingAnchor.constraint(equalTo: fundTransferView.trailingAnchor).isActive = true
billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: 130).isActive = true
}
}
I know there is a small mistake I'm doing but not sure. Help would be appreciated, Thank you.
You are missing a couple constraints...
If you add this at the end of viewDidLoad():
subscriptionView.clipsToBounds = true
fundTransferView.clipsToBounds = true
billPaymentView.clipsToBounds = true
You'll see that the views "disappear":
because they have no Width or Height constraints (so their size is .zero).
In setupConstraints() in your PaymentServiceView class, add these two lines:
self.widthAnchor.constraint(equalTo: self.views.widthAnchor).isActive = true
self.heightAnchor.constraint(equalTo: self.views.heightAnchor).isActive = true
Now, the PaymentServiceView will be the same Width and Height as self.views view.
However, now that the views have Heights:
your layout needs to be adjusted.
Change the .clipsToBounds back to false (remove those added lines) so the shadows won't be clipped, and change these constraints in your controller (adjust the 15 and -15 to your liking):
//subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: -130).isActive = true
subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: 15).isActive = true
//billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: 130).isActive = true
billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: -15).isActive = true
and we get:
Now, because the views have a size, the Tap Gestures will work.

add ui label to ui imageview on collectionview

I am new to swift - I want to add a label ONTO the ui image view image in a collectionview, however i am having some issues. The text sits below the image view, how can i adjust it so it stays in the middle of the image. I want to do this in code and not use storyboards or a nib file. I believe it may be due to my frames i have set:
import UIKit
class ExploreCollectionViewCell: UICollectionViewCell {
static let identifier = "ExploreCollectionViewCell"
private let label: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = UIFont(name: Settings.shared.MAIN_APP_FONT_BOLD, size: 13)
return label
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 0
imageView.contentMode = .scaleAspectFill
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(label)
contentView.addSubview(imageView)
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 5, y: 0, width: contentView.frame.size.width-10, height: 50)
//imageView.frame = contentView.bounds
imageView.frame = CGRect(x: 0, y: 0, width: 250, height: 250)
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
}
func configure(with urlString: String, label: String) {
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, error == nil else {
return
}
DispatchQueue.main.async {
self?.label.text = label
let image = UIImage(data: data)
self?.imageView.image = image
}
}
task.resume()
}
}
First, add the imageView before adding the label, so that it will be shown behind the label:
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(imageView) // behind
contentView.addSubview(label) // front
}
Second, it would be easier to use auto layout:
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 250).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 250).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
label.centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true
label.widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = UIFont(name: Settings.shared.MAIN_APP_FONT_BOLD, size: 13)
return label
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 0
imageView.contentMode = .scaleAspectFill
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(label)
contentView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
label.heightAnchor.constraint(equalToConstant: 50).isActive = true
label.centerYAnchor.constraint(contentView.centerYAnchor).isActive = true
label.centerXAnchor.constraint(contentView.centerXAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true
}
required init?(coder: NSCoder) {
fatalError()
}

Vertical ScrollView not scrolling (No Storyboard)

I need help creating a Scroll View without Storyboards. Here is my code for setting up the Scroll View; I'm not setting a contentSize of the Scroll View because I'd like the scroll view content size to be dynamic, dependent on the amount of text in the TextView. What I did instead, is I tried adding a 'contentView' to the Scroll View and added all my UI elements into the contentView. Any help would be appreciated.
import Foundation
import UIKit
import UITextView_Placeholder
class ComposerVC: UIViewController {
private var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
return content
}()
private var title: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.placeholder = "Untitled"
title.textColor = UIColor(hexString: "#50E3C2")
title.font = UIFont(name: "Rubik-BoldItalic", size: 32)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
private var divider: UIView = {
let divider = UIView()
divider.translatesAutoresizingMaskIntoConstraints = false
divider.backgroundColor = UIColor(hexString: "#50E3C2")
return divider
}()
private var content: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.placeholder = "Begin writing here..."
title.textColor = .white
title.font = UIFont(name: "Avenir-Book", size: 15)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
override func viewDidLoad() {
setupUI()
setupUIConstraints()
title.delegate = self
}
private func setupUI() {
view.backgroundColor = UIColor(hexString: "#131415")
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(title)
contentView.addSubview(divider)
contentView.addSubview(content)
}
private func setupUIConstraints() {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
title.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 95).isActive = true
title.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
title.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
divider.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 15).isActive = true
divider.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
divider.heightAnchor.constraint(equalToConstant: 1).isActive = true
divider.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.8).isActive = true
content.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 15).isActive = true
content.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
content.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
}
}
extension ComposerVC: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textView.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
}
}
A couple tips:
Don't use existing names for variables... with your code as-is, private var title: UITextView causes problems (title is already a view controller property).
Use var names that imply the object... e.g. titleTextView and contentTextView instead of title and content
During development - particularly when you're working on layout - give your UI elements contrasting background colors so you can easily see their frames at runtime.
When using code-created views, set .clipsToBounds = true ... if you don't see any subviews you've added, you know the frame / constraints are missing something.
I don't have your UITextView_Placeholder import, but that shouldn't affect anything here...
So, first, change your viewDidLoad() to this:
override func viewDidLoad() {
setupUI()
setupUIConstraints()
titleTextView.delegate = self
// contrasting colors during development
scrollView.backgroundColor = .red
titleTextView.backgroundColor = .yellow
contentTextView.backgroundColor = .green
divider.backgroundColor = .blue
contentView.backgroundColor = .cyan
}
When you run it, you should see (scroll view background is red, and this is without the placeholder stuff):
It looks correct, except there's no cyan-colored contentView.
Now, clip the subviews of contentView:
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
// add this line
content.clipsToBounds = true
return content
}()
The result:
Where did everything go? Well, we didn't see the cyan contentView and now we don't see any of its subviews ... If we use Debug View Hierarchy we can find out contentView has a Height of Zero.
Fix that by constraining the bottom of the second text view in setupUIConstraints() (I've renamed it to contentTextView instead of just content):
contentTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -95).isActive = true
and we get:
Now the Height of the cyan contentView is controlled by correctly setup constraints of its subviews.
As a side note: with constraints setup properly, and scrolling disabled for the text views, you do not need your:
extension ComposerVC: UITextViewDelegate {
//func textViewDidChange(_ textView: UITextView) {
//...
//}
}
The text view will automatically size itself to its text:
Here's the complete edited code:
class ComposerVC: UIViewController {
private var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: UIScreen.main.bounds)
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
private var contentView: UIView = {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
// add this line so we know if the constraints are set correctly
content.clipsToBounds = true
return content
}()
private var titleTextView: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
// title.placeholder = "Untitled"
title.textColor = UIColor(hexString: "#50E3C2")
title.font = UIFont(name: "Rubik-BoldItalic", size: 32)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
private var divider: UIView = {
let divider = UIView()
divider.translatesAutoresizingMaskIntoConstraints = false
divider.backgroundColor = UIColor(hexString: "#50E3C2")
return divider
}()
private var contentTextView: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
// title.placeholder = "Begin writing here..."
title.textColor = .white
title.font = UIFont(name: "Avenir-Book", size: 15)
title.backgroundColor = .clear
title.isScrollEnabled = false
return title
}()
override func viewDidLoad() {
setupUI()
setupUIConstraints()
titleTextView.delegate = self
// contrasting colors during development
scrollView.backgroundColor = .red
titleTextView.backgroundColor = .yellow
contentTextView.backgroundColor = .green
divider.backgroundColor = .blue
contentView.backgroundColor = .cyan
}
private func setupUI() {
view.backgroundColor = UIColor(hexString: "#131415")
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(titleTextView)
contentView.addSubview(divider)
contentView.addSubview(contentTextView)
}
private func setupUIConstraints() {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
titleTextView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 95).isActive = true
titleTextView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
titleTextView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
divider.topAnchor.constraint(equalTo: titleTextView.bottomAnchor, constant: 15).isActive = true
divider.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
divider.heightAnchor.constraint(equalToConstant: 1).isActive = true
divider.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.8).isActive = true
contentTextView.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 15).isActive = true
contentTextView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 35).isActive = true
contentTextView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -35).isActive = true
contentTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -95).isActive = true
}
}
extension ComposerVC: UITextViewDelegate {
// func textViewDidChange(_ textView: UITextView) {
// let fixedWidth = textView.frame.size.width
// let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
// textView.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
// }
}
Assuming you are using iOS 11+, Your contentView should have its anchors constrained to the contentLayoutGuide of the scrollView. like this:
contentView
.leadingAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor).isActive = true
contentView
.topAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor).isActive = true
contentView
.trailingAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true
contentView
.bottomAnchor
.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor).isActive = true
Also, its width should be constrained to the scrollView's frameLayoutGuide and not the view's width, like this:
contentView
.widthAnchor
.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
That should make the scrollView detect the content size to fit.

Text jumps while automatically resize textView in swift

if have a simple messageView in my app. on the messageView is a input-container with a textView. The textView should resize depending on its content.
It works so far, but every time while wrapping into the next line the text "jumps" for the first character, but reposition with the second character. it looks like:
the most of my code. i assume that it has something to do with the scroll capabilities of the textView(?)
private let container: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
view.layer.borderColor = UIColor(red:0.90, green:0.90, blue:0.90, alpha:1.0).cgColor
view.layer.borderWidth = 0.5
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var inputTV: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.font = UIFont(name: "OpenSans-Light", size: 16)
tv.backgroundColor = .red
tv.delegate = self
tv.textContainer.lineBreakMode = .byWordWrapping
return tv
}()
override internal init(frame: CGRect) {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
container.addSubview(inputTV)
container.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
container.leftAnchor.constraint(equalTo: leftAnchor , constant: 16).isActive = true
container.rightAnchor.constraint(equalTo: rightAnchor, constant: -16).isActive = true
containerHeightAnchor = container.heightAnchor.constraint(equalToConstant: 40)
containerHeightAnchor?.isActive = true
inputTV.leftAnchor.constraint(equalTo: uploadButton.rightAnchor).isActive = true
inputTV.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
inputTV.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -5).isActive = true
textViewHeightAnchor = inputTV.heightAnchor.constraint(equalTo: container.heightAnchor)
textViewHeightAnchor?.isActive = true
}
internal func textViewDidChange(_ textView: UITextView) {
let contentHeight = textView.contentSize.height
containerHeightAnchor?.constant = max(contentHeight, 40)
inputTV.frame.size.height = contentHeight
}
I hope somebody could help. regards
thanks to #DonMag i do remove the heightAnchor from textViewand it does work for me. The new code is as following:
disable scrolling on the textView (isScrollEnabled = false)
private lazy var inputTV: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.font = UIFont(name: "OpenSans-Light", size: 16)
tv.delegate = self
tv.isScrollEnabled = false
return tv
}()
remove heightAnchor (no inputTV.heightAnchor resp. my textViewHeightAnchor)
override internal init(frame: CGRect) {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
addSubview(container)
container.addSubview(inputTV)
...
inputTV.leftAnchor.constraint(equalTo: uploadButton.rightAnchor).isActive = true
inputTV.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
inputTV.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -5).isActive = true
}

Swift 4: Add View on top of all controllers

Conditions:
Swift 4, Xcode 9.3
Target: iOS 11.3
UI Done Programatically
Using Constraints
My Root View Controller is a Navigation
Situation:
I wanted to float an audio player that will be visible throughout the app.
I did an AudioPlayer.swift class that contains the user interface of the audio player.
AudioPlayer.swift
import Foundation
import UIKit
import FRadioPlayer
class AudioPlayer: UIView {
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
setupAudioControls()
}
private func setupAudioControls(){
let appDelegate = AppDelegate.sharedInstance
self.backgroundColor = UIColor.init(hex: "#EBE4D3")
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: appDelegate().rootView ).isActive = true
self.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
Problem:
How can I add this to the Root View Controller to stay on top in all view controllers that I have in my app? Wherever I navigate, the player must stay on the bottom part of every controller. As you can see, I need a reference to the rootviewcontroller to set the contraints for the AudioPlayer but I failed in so many attempts (like calling the rootviewcontroller using AppDelegate)
I update it for you
add singleton static let shared = AudioPlayer()
add public func showAudioPlayer () --> to display Audio player
add as subview to UIApplication.shared.keyWindow?
TODO- add HideAudioPlayer()
Use like this
AudioPlayer.shared.showAudioPlayer()
Here is updated code
import Foundation
import UIKit
class AudioPlayer: UIView {
static let shared = AudioPlayer()
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
// setupAudioControls()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func showAudioPlayer (){
self.setupAudioControls()
}
private func setupAudioControls(){
self.backgroundColor = .red
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
UIApplication.shared.keyWindow?.addSubview(self)
if let layoutGuide = UIApplication.shared.keyWindow?.layoutMarginsGuide {
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor ).isActive = true
self.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
}
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
If you want to show view in each view controller then as per view hierarchy you must have to add in UIWindow. UIWindow is base of all screen.
AppDelegate.shared.window?.addSubview(AudioPlayer)
You can add your view to UIWindow.
I am doing the same thing with below method in AppDelegate.
var window: UIWindow?
func addPlayerViewAtBottom() {
var bottomView : PlayerBottomView!
bottomView = PlayerBottomView(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 60, width: UIScreen.main.bounds.width, height: 60))
self.window?.addSubview(bottomView)
self.window?.bringSubview(toFront: bottomView)
}

Resources