How to use UITextView text as mask to cutout background in swift? - swift

It is easy to do a background cutout with a normal label, using the following code for example:
var coloredBackground : UIView!
coloredBackground = UIView(frame: view.bounds)
coloredBackground.backgroundColor = .blue
coloredBackground.center = view.center
view.addSubview(coloredBackground)
let imageView = UIImageView(frame: coloredBackground.bounds)
imageView.image = UIImage(named: "image")
coloredBackground.addSubview(imageView)
let label = UILabel(frame: coloredBackground.bounds)
label.text = "stackoverflow"
label.font = UIFont(name: "Helvetica", size: 30)
coloredBackground.addSubview(label)
imageView.mask = label
However I cannot seem to apply it to a UITextView
Here is the desired effect

Related

Background Color to imageView

I am trying to add a circle background to an image view. But I only see the image without background.
lazy var circle: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
view.image = UIImage(named: "iconsCancelThin")
view.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
view.tintColor = KSColor.neutral700.getColor()
view.backgroundColor = KSColor.neutral700.getColor()
view.clipsToBounds = true
view.layer.cornerRadius = 16
return view
}()
guard let image = circle.image else { return }
self.cancelButton.setBackgroundImage(image, for: .normal, barMetrics: .default)

How to add a subView to all UITable Cells

Without using storyboard.
I'm trying to add an error label to any cell where a value is not filled out/saved. I concluded that I dont need to show this logic and the issue is showing more than one error labels in all/more than one tableView's cell.
I've created this viewLabel to reuse:
struct Label {
static let errorLabel: UILabel = {
let label = UILabel()
label.frame = CGRect(x: 0, y: 0, width: 18, height: 18)
label.text = "!"
label.layer.cornerRadius = label.frame.height / 2
label.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.white
label.font = UIFont(name: "CircularStd-Black", size: 14)
label.clipsToBounds = true
return label
}()
}
Inside cellForRowAt:
// I'm using detailTextLabel
let cell = UITableViewCell(style: .value1, reuseIdentifier: cellId)
cell.addSubview(Label.errorLabel)
// [...] constraints for Label.errorLabel
return cell
Based on this example, I'd expect to show a red circle on all cells but instead, it shows on the last cell. Why?
A few things wrong here:
You should only add to cell contentView. (https://developer.apple.com/documentation/uikit/uitableviewcell/1623229-contentview)
Example:
cell.contentView.addSubview(myLabel)
Better reuse would be to add your, label once.
This can be done in interface builder or init or awakeFromNib. This way reuse will be more efficient.
And this is the main issue you are seeing:
You are adding one static label, again and again.
Meaning: only the last cell will display it because there is only one label (:
Better use a function to create the label (Factory function)
static func createLabel() -> UILabel {
let label = UILabel()
label.frame = CGRect(x: 0, y: 0, width: 18, height: 18)
label.text = "!"
label.layer.cornerRadius = label.frame.height / 2
label.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.white
label.font = UIFont(name: "CircularStd-Black", size: 14)
label.clipsToBounds = true
return label
}

Add label in center of imageView

I have the two images like in the picture at the end of the question (the image of a list and a red dot). I want to add a label in the center of the red dot. This is my code that doesn't work:
image = UIImageView(image: UIImage(named: "pallino"))
image.frame = CGRect(x: 55, y: self.view.frame.height-60, width: 22, height: 22)
self.view.addSubview(image)
image.layer.cornerRadius = image.frame.width/2
label = UILabel(frame: CGRect(x: self.image.center.x, y: self.image.center.y, width: image.frame.size.width, height: image.frame.size.height))
label.text = "4"
label.font = UIFont(name:"HelveticaNeue-Bold", size: 15.0)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
image.addSubview(label)
Can someone tell me were am I wrong?
Problem in this line:
label = UILabel(frame: CGRect(x: self.image.center.x, y: self.image.center.y, width: image.frame.size.width, height: image.frame.size.height))
self.image.center.x - The center point is specified in points in the coordinate system of its superview, it is mean that self.image.center is not center of image
You need frame for label, something like this:
let imageSize = 22
let frame = CGRect(x: 0, y: 0, width: imageSize, height: imageSize)
let label = UILabel(frame: frame)
label.aligment = .center
You can set constraint in your label(centered Horizontally and Vertically). Try with the following code.
let label = UILabel()
label.text = "4"
label.font = UIFont(name:"HelveticaNeue-Bold", size: 15.0)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
image.addSubview(label)
label.centerXAnchor.constraint(equalTo: self.image.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: self.image.centerYAnchor).isActive = true
To center the label in UIImageView you can refer to this example which is tested and working solution.
extension UIImageView {
/// Create label programmatically
/// - Returns: UILabel
private func ageSensitiveLabel() -> UILabel {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height))
label.text = "Content"
label.font = .systemFont(ofSize: 5.0)
label.textAlignment = .center
label.numberOfLines = 0
label.minimumScaleFactor = 0.5
label.adjustsFontSizeToFitWidth = true
label.baselineAdjustment = .alignCenters
label.textColor = .white
return label
}
/// Add label as subview to UIImageView
func addAgeSensitiveLabel() {
self.subviews.forEach { view in
DispatchQueue.main.async {
view.removeFromSuperview()
}
}
self.addSubview(ageSensitiveLabel())
}
}
Use it with UIImageView
imageView.addAgeSensitiveLabel()

Add subtitle under the title in navigation bar controller in Xcode

So I'm wanting to add a "subtitle" under the title in the navigation bar in navigation controller.
Mostly everything I look up so far wants me to use CGRect. I don't know a whole lot what that is and it sounds like its wanting me to create an entire new view which is not what I am wanting to do.
My question is, is there a dot method to adding a subtitle view easily?
The closest thing I found was posted on stack overflow and here is the link:
Create a subtitle in navigationbar
Apparently last year this worked but now I am getting errors and it's in my viewDidLoad...
I tried this:
self.navigationController?.navigationItem.prompt = "Subtitle Here"
It's the only thing that won't show any errors but still doesn't work. It literally does nothing. At least nothing visible at run time.
On a side note, swift is preferred. Thanks!
Here is my version using a stack view on an extension.
extension UINavigationItem {
func setTitle(title:String, subtitle:String) {
let one = UILabel()
one.text = title
one.font = UIFont.systemFont(ofSize: 17)
one.sizeToFit()
let two = UILabel()
two.text = subtitle
two.font = UIFont.systemFont(ofSize: 12)
two.textAlignment = .center
two.sizeToFit()
let stackView = UIStackView(arrangedSubviews: [one, two])
stackView.distribution = .equalCentering
stackView.axis = .vertical
stackView.alignment = .center
let width = max(one.frame.size.width, two.frame.size.width)
stackView.frame = CGRect(x: 0, y: 0, width: width, height: 35)
one.sizeToFit()
two.sizeToFit()
self.titleView = stackView
}
}
Though there is a solution but it has some known issues
Solution is writing a function like this
func setTitle(title:String, subtitle:String) -> UIView {
let titleLabel = UILabel(frame: CGRectMake(0, -2, 0, 0))
titleLabel.backgroundColor = UIColor.clearColor()
titleLabel.textColor = UIColor.grayColor()
titleLabel.font = UIFont.boldSystemFontOfSize(17)
titleLabel.text = title
titleLabel.sizeToFit()
let subtitleLabel = UILabel(frame: CGRectMake(0, 18, 0, 0))
subtitleLabel.backgroundColor = UIColor.clearColor()
subtitleLabel.textColor = UIColor.blackColor()
subtitleLabel.font = UIFont.systemFontOfSize(12)
subtitleLabel.text = subtitle
subtitleLabel.sizeToFit()
let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))
titleView.addSubview(titleLabel)
titleView.addSubview(subtitleLabel)
let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width
if widthDiff < 0 {
let newX = widthDiff / 2
subtitleLabel.frame.origin.x = abs(newX)
} else {
let newX = widthDiff / 2
titleLabel.frame.origin.x = newX
}
return titleView
}
Using this function for custom navigation title view in viewDidLoad
self.navigationItem.titleView = setTitle("Title", subtitle: "SubTitle")
Only known issue is that if subtitle becomes very large than the misplacement occurs.
Final Outcome
Source: https://gist.github.com/nazywamsiepawel/0166e8a71d74e96c7898
#iosjillian's Swift 4 extension works great, adding a bit more to honor the bar's appearance and user font preferences:
import UIKit
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let appearance = UINavigationBar.appearance()
let textColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.textColor = textColor
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
subtitleLabel.textColor = textColor.withAlphaComponent(0.75)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
self.titleView = stackView
}
}
Thanks a lot for your answer! #RajanMaheshwari
Your coding worked perfectly except the if statement you made with the widthDiff..
I adjusted it a little bit and everything worked smoothly.
if widthDiff < 0 {
let newX = widthDiff / 2
subtitleLabel.frame.origin.x = abs(newX)
} else {
let newX = widthDiff / 2
titleLabel.frame.origin.x = newX
}
Thanks again for your response!
I really liked #user2325031's answer, but found that sizing the labels to fit and setting the frame wasn't needed. I also set the stackView's alignment to .center per #GerardoMR's suggestion.
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 17.0)
titleLabel.textColor = .black
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .systemFont(ofSize: 12.0)
subtitleLabel.textColor = .gray
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
self.titleView = stackView
}
}
In case anyone looking for Objective-C code of the above mentioned solution:
UILabel *title = [[UILabel alloc]init];
UILabel *subtitle = [[UILabel alloc]init];
[title setFont:[UIFont systemFontOfSize:12]];
[title setTextColor:[UIColor whiteColor]];
[title setFont:[UIFont systemFontOfSize:17]];
[title sizeToFit];
title.text = #"Title";
[subtitle setTextColor:[UIColor whiteColor]];
[subtitle setFont:[UIFont systemFontOfSize:12]];
[subtitle setTextAlignment:NSTextAlignmentCenter];
[subtitle sizeToFit];
subtitle.text = #"Subtitle Title";
UIStackView *stackVw = [[UIStackView alloc]initWithArrangedSubviews:#[title,subtitle]];
stackVw.distribution = UIStackViewDistributionEqualCentering;
stackVw.axis = UILayoutConstraintAxisVertical;
stackVw.alignment =UIStackViewAlignmentCenter;
[stackVw setFrame:CGRectMake(0, 0, MAX(title.frame.size.width, subtitle.frame.size.width), 35)];
self.navigationItem.titleView = stackVw;
Thanks for the answer #RajanMaheshwari
If anyone is having the issue where the title becomes misaligned when the subtitle text is longer than the title text, I added the following code to the Rajan's answer above just below where the subtitleLabel is instantiated:
// Fix incorrect width bug
if (subtitleLabel.frame.size.width > titleLabel.frame.size.width) {
var titleFrame = titleLabel.frame
titleFrame.size.width = subtitleLabel.frame.size.width
titleLabel.frame = titleFrame
titleLabel.textAlignment = .center
}
Hope this helps someone who encountered the same issue as me
Another solution, using only one label and NSAttributedString to differentiate between title and subtitle (with different font sizes, weights, colors, etc.) instead. Removes the problem of different label alignment.
extension UIViewController {
func setTitle(_ title: String, subtitle: String) {
let rect = CGRect(x: 0, y: 0, width: 400, height: 50)
let titleSize: CGFloat = 20 // adjust as needed
let subtitleSize: CGFloat = 15
let label = UILabel(frame: rect)
label.backgroundColor = .clear
label.numberOfLines = 2
label.textAlignment = .center
label.textColor = .black
let text = NSMutableAttributedString()
text.append(NSAttributedString(string: title, attributes: [.font : UIFont.boldSystemFont(ofSize: titleSize)]))
text.append(NSAttributedString(string: "\n\(subtitle)", attributes: [.font : UIFont.systemFont(ofSize: subtitleSize)]))
label.attributedText = text
self.navigationItem.titleView = label
}
}
Custom titleView based in part on https://stackoverflow.com/a/34298491/3918865
Swift 4:
import UIKit
class NavigationTitleView: UIView {
private var contentStackView = UIStackView()
private var titleLabel = UILabel()
private var subTitleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
viewConfig()
addViewsConfig()
layoutViewsConfig()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(title: String, subTitle: String){
self.titleLabel.text = title
self.subTitleLabel.text = subTitle
}
private func viewConfig() {
contentStackView.axis = .vertical
contentStackView.alignment = .center
contentStackView.distribution = .fill
contentStackView.spacing = 5
self.backgroundColor = .clear
self.titleLabel.textColor = .white
self.self.subTitleLabel.textColor = .white
}
private func addViewsConfig() {
contentStackView.addArrangedSubview(subTitleLabel)
contentStackView.addArrangedSubview(titleLabel)
self.addSubview(contentStackView)
}
private func layoutViewsConfig(){
contentStackView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0.0).isActive = true
contentStackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
}
}
Use:
import UIKit
class ViewController: UIViewController {
private var navigationTitleView = NavigationTitleView()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.titleView = navigationTitleView
navigationTitleView.set(title: "title", subTitle: "subTitle")
}
}
Easy fix for iOS 16. Following the #iosjillian's / #Dan's approx, calling layoutSubviews() on stack view does the trick.
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let textColor = getTextColor()
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.textColor = textColor
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
subtitleLabel.textColor = textColor.withAlphaComponent(0.75)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
stackView.layoutSubviews()
self.titleView = stackView
}
}
Working iOS 16 solution. Swift 5.7
With SnapKit library. If you are not using SnapKit lib, just make both views (titleLabel and subtitleLabel) translatesAutoresizingMaskIntoConstraints = false and replace SnapKit Constraints with native constraints.
func setTitle(title: String, subtitle: String, view: UIView) -> UIView {
let appearance = UINavigationBar.appearance()
let titleColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black
let titleLabel = UILabel()
titleLabel.backgroundColor = UIColor.clear
titleLabel.textColor = titleColor
titleLabel.adjustsFontSizeToFitWidth = false
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.textAlignment = .center
titleLabel.text = title
let subtitleLabel = UILabel()
subtitleLabel.backgroundColor = UIColor.clear
subtitleLabel.textColor = UIColor.init(hexString: "#808890")
subtitleLabel.adjustsFontSizeToFitWidth = false
subtitleLabel.lineBreakMode = .byTruncatingTail
subtitleLabel.textAlignment = .center
subtitleLabel.font = UIFont.systemFont(ofSize: 11)
subtitleLabel.text = subtitle
let titleView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
titleView.addSubview(titleLabel)
titleView.addSubview(subtitleLabel)
titleLabel.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview()
make.top.equalToSuperview().offset(-20)
make.height.equalTo(20)
}
subtitleLabel.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.height.equalTo(10)
}
return titleView
}

Changing the color of the icons in a UItextField inside a UISearchBar

I'm trying to customise the appearance of the search bar in my search controller.
Setting the background and text colors works fine but I just didn't find a way to change the color of the icons in the text field, specifically the magnifying glass and the x button.
I've found this Objective-C code which should do what I want but I'm struggling to translate it to Swift:
(EDIT: Skip to the first answer for the working Swift 3 solution.)
UITextField *searchBarTextField = [self.searchDisplayController.searchBar valueForKey:#"_searchField"];
// Magnifying glass icon.
UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView;
leftImageView.image = [LeftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
leftImageView.tintColor = [UIColor whiteColor];
// Clear button
UIButton *clearButton = [searchBarTextField valueForKey:#"_clearButton"];
[clearButton setImage:[clearButton.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
clearButton.tintColor = [UIColor whiteColor];
My attempt to translate to Swift:
let textField = searchController.searchBar.valueForKey("searchField") as! UITextField
// These two work fine.
textField.backgroundColor = UIColor.blackColor()
textField.textColor = UIColor.blackColor()
var glassIcon = textField.leftView
// This would work.
//glassIcon.hidden = true
// This does not have any effect.
//glassIcon?.tintColor = UIColor.whiteColor()
// My attempt to translate, but it gives an error.
glassIcon.image? = UIImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
var clearButton = textField.valueForKey("clearButton")!
clearButton.setImage(clearButton.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
// This gives the error: "Cannot assign to property: 'clearButton' is immutable
clearButton.tintColor = UIColor.whiteColor()
// Sorry for the weird formatting, it glitches here in the editor.
The leftView does not seem to have an image property. How can I access that property as the Objective-C code does?
Also, if there is a better to achieve what I want please let me know.
Here is the solution:
// Text field in search bar.
let textField = searchController.searchBar.value(forKey: "searchField") as! UITextField
let glassIconView = textField.leftView as! UIImageView
glassIconView.image = glassIconView.image?.withRenderingMode(.alwaysTemplate)
glassIconView.tintColor = UIColor.white
let clearButton = textField.valueForKey("clearButton") as! UIButton
clearButton.setImage(clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate), for: .normal)
clearButton.tintColor = UIColor.white
Here is the solution:
extension UITextField{
func setLeftIcon(_ icon: UIImage) {
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.image = icon.withRenderingMode(.alwaysTemplate)
iconView.tintColor = UIColor.white
outerView.addSubview(iconView)
leftView = outerView
leftViewMode = .always
}
}