How to access text views in custom collection view cell class from the main class - ios

I've got two text views in each of my collection view cells, I've made a custom class for the cells. here is the code:
class CustomWriterPageCell: UICollectionViewCell {
fileprivate let textViewOne: UITextView = {
let tv = UITextView()
tv.backgroundColor = .cyan
tv.text = "Chapter Title"
tv.font = UIFont(name: "Avenir-Roman", size: 27)
tv.textColor = .gray
return tv
}()
fileprivate let textViewTwo: UITextView = {
let tv = UITextView()
tv.textColor = .gray
tv.text = "Start your story..."
tv.textContainerInset = UIEdgeInsets(top: 20, left: 15, bottom: 20, right: 15)
tv.font = UIFont(name: "Avenir-Book", size: 23)
tv.backgroundColor = .black
return tv
}()
}
I would like to add placeholders to both these text views but the problem is that since they are in a custom class there is no way, that I know of, to find out which text view is being edited so I can't add the respective placeholders back when textViewDidEndEditing takes place, is there a way to find out which text view is being edited? Is there a way to access the text views from the main class?

In your CustomWriterPageCell class:
fileprivate let textViewOne: UITextView = {
let tv = UITextView()
tv.backgroundColor = .cyan
tv.text = "Chapter Title"
tv.font = UIFont(name: "Avenir-Roman", size: 27)
tv.textColor = .gray
return tv
}()
fileprivate let textViewTwo: UITextView = {
let tv = UITextView()
tv.textColor = .gray
tv.text = "Start your story..."
tv.textContainerInset = UIEdgeInsets(top: 20, left: 15, bottom: 20, right: 15)
tv.font = UIFont(name: "Avenir-Book", size: 23)
tv.backgroundColor = .black
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
textViewOne.delegate = self
textViewTwo.delegate = self
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then:
extension CustomWriterPageCell : UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
if textView == textViewOne {
textView.text = "..."
}
else if textView == textViewTwo {
textView.text = "..."
}
}
}
This way, your View Controller still does not need to know about your text views.

You can always access the actual textView and know which one is being edited in delegate methods, just like this:
class ViewController: UIViewController {
var textViewA: UITextView = .init()
var textViewB: UITextView = .init()
override func viewDidLoad() {
super.viewDidLoad()
textViewA.delegate = self
textViewB.delegate = self
}
}
extension ViewController: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
switch textView {
case textViewA:
textView.text = "Placeholder A"
case textViewB:
textView.text = "Placeholder B"
default:
break
}
}
}
Also, because of the fact that UITextView doesn't have any built-in placeholder mechanism - you could use this nice pod: https://github.com/devxoul/UITextView-Placeholder
If you use this pod and don't want to make textViews public/internal - you can create computedProperties for it:
var placeholder: String {
get {
return textView.placeholder
} set {
textView.placeholder = newValue
}
}

Related

Can't hide the top and bottom lines in custom searchBar

I´ve tried to change background color inside class SearchBarView: UIView {}:
searchBar.searchTextField.backgroundColor = .clear
searchBar.backgroundColor = .clear
and tryed something like that inside MainViewController:
searchBar.searchTextField.backgroundColor = .clear
searchBar.backgroundColor = .clear
searchBar.layer.backgroundColor = UIColor.clear.cgColor
but, unfortunately I still see this lines inside my custom searchBar.
How can I get rid of these lines?
My SearchBarView class:
class SearchBarView: UIView {
lazy var searchBar = createSearchBar()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(searchBar)
searchBar.snp.makeConstraints { make in
make.leading.equalTo(32)
make.centerY.equalToSuperview()
make.height.equalTo(34)
make.width.equalTo(300)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
fileprivate extension SearchBarView {
private func createSearchBar() -> UISearchBar {
let searchBar = UISearchBar()
searchBar.placeholder = " Search"
searchBar.searchTextField.font = UIFont(name: "MarkPro", size: 15)
searchBar.searchTextField.backgroundColor = .clear
let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField
let imageV = textFieldInsideSearchBar?.leftView as! UIImageView
imageV.image = imageV.image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
imageV.tintColor = UIColor(hexString: "FF6E4E")
return searchBar
}
}
My MainViewController class:
class MainViewController: UIViewController {
private var searchBarView: SearchBarView!
override func viewDidLoad() {
super.viewDidLoad()
setupSearchBarView()
}
private func setupSearchBarView() {
searchBarView = SearchBarView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
view.addSubview(searchBarView)
searchBarView.searchBar.clipsToBounds = true
searchBarView.searchBar.layer.cornerRadius = 17
searchBarView.searchBar.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
searchBarView.searchBar.searchTextField.clipsToBounds = true
let directionalMargins = NSDirectionalEdgeInsets(top: 0, leading: 24, bottom: 0, trailing: 0)
searchBarView.searchBar.directionalLayoutMargins = directionalMargins
searchBarView.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.top.equalTo(categoriesView.snp.bottom)
make.trailing.equalToSuperview()
make.height.equalTo(60)
}
}
}
If you want to make the top and bottom border lines on the textfield disappear (the dark gray ones), you will want to tweak the text field's border properties rather than the background colors. Try something like this:
searchBar.searchTextField.layer.borderWidth = 0
or
searchBar.searchTextField.layer.borderColor = UIColor.clear.cgColor
and adapt it to fit how you've set up the relevant subviews in your custom search bar.
Set the searchBar background image to empty. This eliminates all background issues you may have such as unwanted lines. For more info reference Apple docs: https://developer.apple.com/documentation/uikit/uisearchbar/1624276-backgroundimage
searchBar.backgroundImage = UIImage()

What is best practice for creating a reusable custom button views?

I have three buttons below that have the same UI, the only differences are the text for the labels and tap gesture actions. It looks like this:
What is the best practice for creating a reusable custom button view based on this situation?
So far I tried using: (1) custom button class but had difficulty implementing a stack view where I can configure the two labels in the button, (2) UIButton extension but an issue where tapping the button caused the app to crash
class SetActivityVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
lazy var firstButton: UIButton = {
let button = UIButton()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapFirst))
button.addGestureRecognizer(tapGesture)
button.setBackgroundImage(Image.setButtonBg, for: .normal)
button.addShadowEffect()
let label = UILabel()
label.text = "No Exercise"
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
label.textColor = .black
let subLabel = UILabel()
subLabel.text = "no exercise or very infrequent"
subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
subLabel.textColor = .gray
let stack = UIStackView(arrangedSubviews: [label, subLabel])
stack.axis = .vertical
stack.alignment = .center
stack.isUserInteractionEnabled = true
stack.addGestureRecognizer(tapGesture)
button.addSubview(stack)
stack.centerInSuperview()
return button
}()
lazy var secondButton: UIButton = {
let button = UIButton()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapSecond))
button.addGestureRecognizer(tapGesture)
button.setBackgroundImage(Image.setButtonBg, for: .normal)
button.addTarget(self, action: #selector(didTapSecond), for: .touchUpInside)
button.addShadowEffect()
let label = UILabel()
label.text = "Light Exercise"
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
label.textColor = .black
let subLabel = UILabel()
subLabel.text = "some light cardio/weights a few times per week"
subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
subLabel.textColor = .gray
let stack = UIStackView(arrangedSubviews: [label, subLabel])
stack.axis = .vertical
stack.alignment = .center
button.addSubview(stack)
stack.centerInSuperview()
return button
}()
lazy var thirdButton: UIButton = {
let button = UIButton()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapThird))
button.addGestureRecognizer(tapGesture)
button.setBackgroundImage(Image.setButtonBg, for: .normal)
button.addTarget(self, action: #selector(didTapSecond), for: .touchUpInside)
button.addShadowEffect()
let label = UILabel()
label.text = "Moderate Exercise"
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
label.textColor = .black
let subLabel = UILabel()
subLabel.text = "lifting/cardio regularly but not super intense"
subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
subLabel.textColor = .gray
let stack = UIStackView(arrangedSubviews: [label, subLabel])
stack.axis = .vertical
stack.alignment = .center
button.addSubview(stack)
stack.centerInSuperview()
return button
}()
#objc func didTapFirst() {
print("Tapped 1")
}
#objc func didTapSecond() {
print("Tapped 2")
}
#objc func didTapThird() {
print("Tapped 3")
}
}
extension SetActivityVC {
fileprivate func setupViews() {
addViews()
constrainViews()
}
fileprivate func addViews() {
view.addSubview(firstButton)
view.addSubview(secondButton)
view.addSubview(thirdButton)
}
// Using TinyConstraints
fileprivate func constrainViews() {
firstButton.centerXToSuperview()
secondButton.centerXToSuperview()
secondButton.topToBottom(of: firstButton, offset: screenHeight * 0.03)
thirdButton.centerXToSuperview()
thirdButton.topToBottom(of: secondButton, offset: screenHeight * 0.03)
}
}
There's no universal answer because each situation is unique, but generally there are several common patterns:
Implement a factory method that would create a button, set up all its properties and return it.
Subclass UIButton and add new behavior and reasonable defaults.
Subclass UIControl for something totally custom, like a control that is composed out several other views.
Now, your particular problem seems to be implementing a reusable button with two differently styled lines of text inside.
Adding labels as subviews to UIButton is something I definitely wouldn't recommend. This breaks accessiblity and you'll have to do a lot of work to support different button states like highlighted or disabled.
Instead, I highly recommend to make use of a great feature of UIButton: it supports attributed strings for title, and titles can be multiline as well because you have access to the button's titleLabel property.
Subclassing UIButton just for reasonable defaults and ease of setup seems like a good choice here:
struct TwoLineButtonModel {
let title: String
let subtitle: String
let action: () -> Void
}
final class TwoLineButton: UIButton {
private var action: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
setUpAppearance()
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with model: TwoLineButtonModel) {
[.normal, .highlighted, .disabled].forEach {
setAttributedTitle(
makeButtonTitle(
title: model.title,
subtitle: model.subtitle,
forState: $0
),
for: $0
)
}
action = model.action
}
#objc private func handleTap(_ sender: Any) {
action?()
}
private func setUpAppearance() {
backgroundColor = .yellow
layer.cornerRadius = 16
titleLabel?.numberOfLines = 0
contentEdgeInsets = UIEdgeInsets(top: 16, left: 8, bottom: 16, right: 8)
}
private func makeButtonTitle(
title: String,
subtitle: String,
forState state: UIControl.State
) -> NSAttributedString {
let centeredParagraphStyle = NSMutableParagraphStyle()
centeredParagraphStyle.alignment = .center
let primaryColor: UIColor = {
switch state {
case .highlighted:
return .label.withAlphaComponent(0.5)
case .disabled:
return .label.withAlphaComponent(0.3)
default:
return .label
}
}()
let secondaryColor: UIColor = {
switch state {
case .highlighted:
return .secondaryLabel.withAlphaComponent(0.3)
case .disabled:
return .secondaryLabel.withAlphaComponent(0.1)
default:
return .secondaryLabel
}
}()
let parts = [
NSAttributedString(
string: title + "\n",
attributes: [
.font: UIFont.preferredFont(forTextStyle: .title1),
.foregroundColor: primaryColor,
.paragraphStyle: centeredParagraphStyle
]
),
NSAttributedString(
string: subtitle,
attributes: [
.font: UIFont.preferredFont(forTextStyle: .body),
.foregroundColor: secondaryColor,
.paragraphStyle: centeredParagraphStyle
]
)
]
let string = NSMutableAttributedString()
parts.forEach { string.append($0) }
return string
}
}
The text styles and colors in my example may not exactly match what you need, but it's easily adjustable and you can take it from here. Move things that should be customizable into the view model while keeping the reasonable defaults as a private implementation. Look into tutorials on NSAttributedString if you're not yet familiar with it, it gives you a lot of freedom in styling texts.

Search Bar textfield cursor color not changing in mac Catalyst it's showing black color ios swift

i also try to change with "searchController.searchBar.searchTextField.tintColor = .white"
but it's not working issue facing after xcode 13 update.
Try to create a custom searchController and into the setup to change the tintColor of all the subviews that are different of UIButton .
Here an example :
class CustomSearchController: UISearchController {
var placeHolder:String?
private var catalogSearchBar = CatalogSearchBar()
override public var searchBar: UISearchBar {
get {
catalogSearchBar.placeholder = placeHolder
return catalogSearchBar
}
}
}
class CatalogSearchBar: UISearchBar {
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
private func setup() {
backgroundColor = Constants.shared.navigationBar.lightModeBgColor
// text field
let textField = searchTextField
textField.subviews.forEach { (view) in
if ((view as? UIButton) != nil) {
view.tintColor = UIColor.white
}
}
textField.frame.size.height = 35
self.searchTextPositionAdjustment = UIOffset(horizontal: 4, vertical: 0)
textField.layer.cornerRadius = 15
textField.placeholder = self.placeholder
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
textField.layer.masksToBounds = true
textField.layer.backgroundColor = UIColor.white.withAlphaComponent(0.25).cgColor
if let view = textField.value(forKey: "backgroundView") as? UIView {
view.removeFromSuperview()
}
textField.font = UIFont(name: "Montserrat-Regular", size: 15)
textField.textColor = UIColor.white
textField.tintColor = UIColor.white
// search icon
let leftView: UIView = {
let image = UIImage(named: "search")
let padding = 8
let size = 20
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: size + padding, height: size) )
let iconView = UIImageView(frame: CGRect(x: padding, y: 0, width: size, height: size))
iconView.tintColor = UIColor.white
iconView.image = image
outerView.addSubview(iconView)
return outerView
}()
textField.leftView = leftView
}
}

Multiline UIButton with each line truncated independently

I'm trying to make a multiline button by subclassing UIButton. To avoid drawing two custom UILabel (I'm still pretty new to Swift/Xcode), I'm using attributed strings for the existing UILabel and splitting lines with a new line character, like so:
func prepareAttributedTitle(_ primaryTitle: String = "", _ secondaryTitle: String = "") {
let title = NSMutableAttributedString()
let first = NSAttributedString(string: primaryTitle, attributes: [
NSForegroundColorAttributeName: tintColor,
NSFontAttributeName: UIFont.systemFont(ofSize: UIFont.systemFontSize, weight: UIFontWeightSemibold)
])
let newLine = NSAttributedString(string: "\n")
let second = NSAttributedString(string: secondaryTitle, attributes: [
NSForegroundColorAttributeName: tintColor.withAlphaComponent(0.75),
NSFontAttributeName: UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
])
title.append(first)
title.append(newLine)
title.append(second)
setAttributedTitle(title, for: .normal)
}
And the result is (sorry, I don't have enough rep to post images):
| This is the long first |
| line |
| Secondary line |
However, I'd like to truncate lines independently, like this:
| This is the long fi... |
| Secondary line |
Is there a way to do this without using two custom UILabels?
Thanks
A single UILabel does not support what you need. You will have to use two single-line labels each set with tail truncation.
I'm answering my own question with what worked for me. Here's my subclass of UIButton, but keep in mind I'm not an experienced developer. There's also some styling and support for the tint color:
import UIKit
#IBDesignable
class FilterButton: UIButton {
let primaryLabel = UILabel()
let secondaryLabel = UILabel()
#IBInspectable var primaryTitle = "" {
didSet {
primaryLabel.text = primaryTitle
}
}
#IBInspectable var secondaryTitle = "" {
didSet {
secondaryLabel.text = secondaryTitle
}
}
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func prepareForInterfaceBuilder() {
primaryTitle = "Primary title"
secondaryTitle = "Secondary title"
commonInit()
}
func commonInit() {
// Force left alignment (FIXME: Use user language direction)
contentHorizontalAlignment = .left
// Set some padding and styling
contentEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
layer.cornerRadius = 5
layer.borderWidth = 1
// Hide button original label
titleLabel?.isHidden = true
// Prepare the primary label
primaryLabel.frame = CGRect(x: contentEdgeInsets.left,
y: contentEdgeInsets.top,
width: frame.width - contentEdgeInsets.left - contentEdgeInsets.right,
height: (frame.height - contentEdgeInsets.top - contentEdgeInsets.bottom) / 2)
primaryLabel.font = UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)
primaryLabel.textColor = tintColor
// primaryLabel.backgroundColor = UIColor.green // For debugging
primaryLabel.lineBreakMode = .byTruncatingMiddle // Truncate first line
primaryLabel.autoresizingMask = .flexibleWidth
addSubview(primaryLabel)
// Prepare the secondary label
secondaryLabel.frame = CGRect(x: contentEdgeInsets.left,
y: contentEdgeInsets.top + primaryLabel.frame.height,
width: frame.width - contentEdgeInsets.left - contentEdgeInsets.right,
height: (frame.height - contentEdgeInsets.top - contentEdgeInsets.bottom) / 2)
secondaryLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
secondaryLabel.textColor = tintColor.withAlphaComponent(0.75)
// secondaryLabel.backgroundColor = UIColor.yellow // For debugging
secondaryLabel.lineBreakMode = .byTruncatingMiddle // Truncate second line
secondaryLabel.autoresizingMask = .flexibleWidth
addSubview(secondaryLabel)
primaryLabel.text = primaryTitle
secondaryLabel.text = secondaryTitle
}
// Support tint color
override func tintColorDidChange() {
super.tintColorDidChange()
layer.borderColor = tintColor.cgColor
layer.backgroundColor = tintColor.withAlphaComponent(0.05).cgColor
primaryLabel.textColor = tintColor
secondaryLabel.textColor = tintColor.withAlphaComponent(0.75)
}
}

How to access programatically created UI object from a calss member function

I created a text view programatically and I have an external function outside viewdidload within which I want to change the contents of that textview. How do I achieve that? Here's the code I'm working with
As things stand. I'm currently getting this error "Value of type 'UIView' has no member 'textView' " on the line
self.view.textView.text = "Yo Dawg!!"
class rootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myTextView = UITextView(frame: CGRectMake(0,0,100,50))
myTextView.backgroundColor = UIColor.clearColor()
myTextView.textColor = UIColor.whiteColor()
myTextView.font = UIFont (name: "Helvetica Neue", size: 20)
myTextView.textAlignment = NSTextAlignment.Center
myTextView.text = "Hello World"
self.view.addSubview(timerView)
}//End view did load
func changeText(){//This function updates the text within the textview
self.view.textView.text = "Yo Dawg!!"
}//End change text function
}//End rootViewController
You could assign an unique tag to the textview and use it to get the reference.
override func viewDidLoad() {
super.viewDidLoad()
let myTextView = UITextView(frame: CGRectMake(0,0,100,50))
myTextView.backgroundColor = UIColor.clearColor()
myTextView.textColor = UIColor.whiteColor()
myTextView.font = UIFont (name: "Helvetica Neue", size: 20)
myTextView.textAlignment = NSTextAlignment.Center
myTextView.text = "Hello World"
myTextView.tag = 1
self.view.addSubview(timerView)
}
func changeText(){
if let myTextView = self.view.viewWithTag(1) as? UITextView {
myTextView.text = "Yo Dawg!!"
}
}
You need to keep a reference to your text view in an instance variable/property, not in a local variable:
class rootViewController: UIViewController {
var myTextView:UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.myTextView = UITextView(frame: CGRectMake(0,0,100,50))
self.myTextView.backgroundColor = UIColor.clearColor()
self.myTextView.textColor = UIColor.whiteColor()
self.myTextView.font = UIFont (name: "Helvetica Neue", size: 20)
self.myTextView.textAlignment = NSTextAlignment.Center
self.myTextView.text = "Hello World"
self.view.addSubview(timerView)
}//End view did load
func changeText(){//This function updates the text within the textview
self.myTextView.text = "Yo Dawg!!"
}//End change text function
}

Resources