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

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
}

Related

How to change the text attribute of a UILabel in Swift?

I've set up a UILabel programmatically and I'm attempting to change the text attribute via a function I call later on in the ViewController however when that function is called the questionLabel.text stays the default value "Welcome".
Essentially what I'm trying to accomplish is:
func changeLabelText() {
questionLabel.text = "New label text"
print(questionLabel.text!)
}
changeLabelText()
// prints "New label text"
however what I'm actually getting is:
func changeLabelText() {
questionLabel.text = "New label text"
print(questionLabel.text!)
}
changeLabelText()
// prints "Welcome"
This is how my label is setup:
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
#IBOutlet var cameraView: UIView!
var questionLabel: UILabel {
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .white
label.textColor = .black
label.text = "Welcome"
label.textAlignment = .center
label.frame = CGRect(x: 65, y: 100, width: 300, height: 65)
return label
}
Any suggestions? Greatly appreciated!
The current
var questionLabel: UILabel {
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .white
label.textColor = .black
label.text = "Welcome"
label.textAlignment = .center
label.frame = CGRect(x: 65, y: 100, width: 300, height: 65)
return label
}
is a computed property so every access gets a new separate instance
questionLabel.text = "New label text" // instance 1
print(questionLabel.text!) // instance 2
instead you need a closure
var questionLabel: UILabel = {
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .white
label.textColor = .black
label.text = "Welcome"
label.textAlignment = .center
label.frame = CGRect(x: 65, y: 100, width: 300, height: 65)
return label
}()
Change your computed variable to a lazy initializer like so:
lazy var questionLabel: UILabel = {
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .white
label.textColor = .black
label.text = "Welcome"
label.textAlignment = .center
label.frame = CGRect(x: 65, y: 100, width: 300, height: 65)
return label
}()
Klamont,
You can try this.
Suppose you want to change the some text of your label you always create two labels for that but it's a wrong approach of changing text color of label. You can use the NSMutableAttributedString for changing the some text color of your label.Firstly, you have to find the the range of text, which you want to change the color of that text and then set the range of your text to the NSMutableAttributedString object as compared to full string and then set your label attributedText with the NSMutableAttributedString object.
Example:
let strNumber: NSString = "Hello Test" as NSString // you must set your
let range = (strNumber).range(of: "Test")
let attribute = NSMutableAttributedString.init(string: strNumber)
attribute.addAttribute(NSForegroundColorAttributeName, value: UIColor.red , range: range)
yourLabel.attributedText = attribute
If you want to use this in many times in your application you can just create the extension of the UILabel and it will make more simple :-
extension UILabel {
func halfTextColorChange (fullText : String , changeText : String ) {
let strNumber: NSString = fullText as NSString
let range = (strNumber).range(of: changeText)
let attribute = NSMutableAttributedString.init(string: fullText)
attribute.addAttribute(NSForegroundColorAttributeName, value: UIColor.red , range: range)
self.attributedText = attribute
}
}
Use your label:-
yourLabel = "Hello Test"
yourLabel.halfTextColorChange(fullText: totalLabel.text!, changeText: "Test")

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

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
}
}

How to set multi line Large title in navigation bar? ( New feature of iOS 11)

I am in process of adding large title in navigation bar in one of the application. The issue is title is little long so I will require to add two lines in large title. How can I add large title with two lines in navigation bar?
This is not about default navigation bar title! This is about large title which is introduced in iOS 11. So make sure you add suggestions by considering large title. Thanks
Based in #krunal answer, this is working for me:
extension UIViewController {
func setupNavigationMultilineTitle() {
guard let navigationBar = self.navigationController?.navigationBar else { return }
for sview in navigationBar.subviews {
for ssview in sview.subviews {
guard let label = ssview as? UILabel else { break }
if label.text == self.title {
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
UIView.animate(withDuration: 0.3, animations: {
navigationBar.frame.size.height = 57 + label.frame.height
})
}
}
}
}
In the UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.title = "This is a multiline title"
setupNavigationMultilineTitle()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupNavigationMultilineTitle()
}
And for setting font and color on the large title:
navigation.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: .red, NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 30)]
Get a navigation item subviews and locate UILabel from it.
Try this and see:
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .automatic
self.title = "This is multiline title for navigation bar"
self.navigationController?.navigationBar.largeTitleTextAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: .largeTitle)
]
for navItem in(self.navigationController?.navigationBar.subviews)! {
for itemSubView in navItem.subviews {
if let largeLabel = itemSubView as? UILabel {
largeLabel.text = self.title
largeLabel.numberOfLines = 0
largeLabel.lineBreakMode = .byWordWrapping
}
}
}
Here is result:
The linebreak solution seems to be problematic when there's a back button. So instead of breaking lines, I made the label auto adjust font.
func setupLargeTitleAutoAdjustFont() {
guard let navigationBar = navigationController?.navigationBar else {
return
}
// recursively find the label
func findLabel(in view: UIView) -> UILabel? {
if view.subviews.count > 0 {
for subview in view.subviews {
if let label = findLabel(in: subview) {
return label
}
}
}
return view as? UILabel
}
if let label = findLabel(in: navigationBar) {
if label.text == self.title {
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.7
}
}
}
Then it needs to be called in viewDidLayoutSubviews() to make sure the label can be found, and we only need to call it once:
private lazy var setupLargeTitleLabelOnce: Void = {[unowned self] in
if #available(iOS 11.0, *) {
self.setupLargeTitleAutoAdjustFont()
}
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let _ = setupLargeTitleLabelOnce
}
If there's any navigationController pop event back to this controller, we need to call it again in viewDidAppear(). I haven't found a better solution for this - there's a small glitch of label font changing when coming back from a pop event:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
setupLargeTitleAutoAdjustFont()
}
}
You could try:
Create a custom UINavigationController
Add the protocol UINavigationBarDelegate to the class definition
Override the function navigationBar(_:shouldPush:)
Activate two lines mode using hidden variable item.setValue(true, forKey: "__largeTitleTwoLineMode")
Make navigationController.navigationBar.prefersLargeTitles = true
(Edit 7/13: I notice that this solution is not support scrollView, so now I'm in research)
I found a perfect solution on Swift5
but sorry for my poor English because I'm Japanese🇯🇵Student.
In case of 2 lines In case of 3 lines
At first, set navigation settings for largeTitle normally in viewDidLoad
//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]//ex) fontSize=26, margin=5, numberOfLines=2
//Set title
title = "multiple large\ntitle is working!"
It is most important point of this solution that font-size at largeTitleTextAttributes equals actual font-size(+margin) multiplied by number of lines.
Description image
Because, default specification of navigationBar attributes may be able to display only 1 line largeTitle.
Although, somehow, I did notice that in case of label-settings(the label which subview of subview of navigationBar) on direct, it can display any number of lines in 1 line of in case of navigationBar attributes.
So, we should do set big font in navigationbar attributes, and set small font in the label(subview of subview of navigationBar), and take into consideration the margins.
Do label settings direct in viewDidAppear like this:
//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
subview.subviews.forEach { subsubview in
guard let label: UILabel = subsubview as? UILabel else { return }
//Label settings on direct.
label.text = title
label.font = UIFont.systemFont(ofSize: fontSize)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
}
})
Therefore, in short, the solution at minimum code is given like this:
import UIKit
class ViewController: UIViewController {
private let fontSize: CGFloat = 26, margin: CGFloat = 5
private let numberOfLines: CGFloat = 2
override func viewDidLoad() {
super.viewDidLoad()
setUpNavigation()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setMultipleLargeTitle()
}
private func setUpNavigation() {
//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]
//Set title
title = "multiple large\ntitle is working!"
}
private func setMultipleLargeTitle() {
//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
subview.subviews.forEach { subsubview in
guard let label: UILabel = subsubview as? UILabel else { return }
//Label settings on direct.
label.text = title
label.font = UIFont.systemFont(ofSize: fontSize)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
}
})
}
}
thank you for reading :)
Swift 4 : Multi line even though the sentence is only short
title = "You're \nWelcome"
for navItem in(self.navigationController?.navigationBar.subviews)! {
for itemSubView in navItem.subviews {
if let largeLabel = itemSubView as? UILabel {
largeLabel.text = self.title
largeLabel.numberOfLines = 0
largeLabel.lineBreakMode = .byWordWrapping
}
}
}
If anyone looking for Title Lable Not Large Title, then below code is working.
Swift 5.X
func setMultilineNavigationBar(topText: String, bottomText : String) {
let topTxt = NSLocalizedString(topText, comment: "")
let bottomTxt = NSLocalizedString(bottomText, comment: "")
let titleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .semibold)]
let subtitleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13, weight: .regular)]
let title:NSMutableAttributedString = NSMutableAttributedString(string: topTxt, attributes: titleParameters)
let subtitle:NSAttributedString = NSAttributedString(string: bottomTxt, attributes: subtitleParameters)
title.append(NSAttributedString(string: "\n"))
title.append(subtitle)
let size = title.size()
let width = size.width
guard let height = navigationController?.navigationBar.frame.size.height else {return}
let titleLabel = UILabel(frame: CGRect.init(x: 0, y: 0, width: width, height: height))
titleLabel.attributedText = title
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .center
self.navigationItem.titleView = titleLabel
}
SWIFT 5 This UIViewController extension helped me. Scenario that I have is mixed with enabling and disabling large titles so FIRST ENABLE large title and then call this method. Call it in viewDidLoad, I have found bug with peeking back with swipe and then releasing touch, for some reason current navigation title become previous navigation title
extension UIViewController {
/// Sets two lines for navigation title if needed
/// - Parameter animated: used for changing titles on one controller,in that case animation is off
func multilineNavTitle(_ animated:Bool = true) {
if animated {
// setting initial state for animation of title to look more native
self.navigationController?.navigationBar.transform = CGAffineTransform.init(translationX: .screenWidth/2, y: 0)
self.navigationController?.navigationBar.alpha = 0
}
//Checks if two lines is needed
if self.navigationItem.title?.forTwoLines() ?? false {
// enabling multiline
navigationItem.setValue(true,
forKey: "__largeTitleTwoLineMode")
} else {
// disabling multiline
navigationItem.setValue(false,
forKey: "__largeTitleTwoLineMode")
}
// laying out title without animation
UIView.performWithoutAnimation {
self.navigationController?.navigationBar.layoutSubviews()
self.navigationController?.view.setNeedsLayout()
self.navigationController?.view.layoutIfNeeded()
}
if animated {
//animating title
UIView.animate(withDuration: 0.3) {
self.navigationController?.navigationBar.transform = CGAffineTransform.identity
self.navigationController?.navigationBar.alpha = 1
}
}
}
}
fileprivate extension String {
/// Checks if navigation title is wider than label frame
/// - Returns: `TRUE` if title cannot fit in one line of navigation title label
func forTwoLines() -> Bool {
let fontAttributes = [NSAttributedString.Key.font: SomeFont]
let size = self.size(withAttributes: fontAttributes)
return size.width > CGFloat.screenWidth - 40 //in my case
}
}
Just create a custom navigation controller. Rest will be handled by the OS itself
class MyNavigationViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
navigationBar.delegate = self
}
}
extension MyNavigationViewController: UINavigationBarDelegate {
func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool {
item.setValuesForKeys([
"__largeTitleTwoLineMode": true
])
return true
}
}
viewController.navigationItem
.setValuesForKeys(["__largeTitleTwoLineMode": true])
WARNING: This method does not work on older OS versions

Subclassing UIViewController With NavigationBar Title

I'm trying to subclass a UIViewController with a label (UILabel) set to the title of the navigation bar. Instead of setting a name to self.title, I want to use an attributed string to set the title.
class BasicViewController: UIViewController {
var titleString = ""
func setup() {
//self.title = titleString
let navBar = navigationController!.navigationBar
navBar.barTintColor = UIColor.redColor()
let atext = NSMutableAttributedString(string: titleString)
atext.addAttribute(NSForegroundColorAttributeName, value: UIColor.whiteColor(), range: NSMakeRange(0, atext.length))
atext.addAttribute(NSStrokeColorAttributeName, value: UIColor.yellowColor(), range: NSMakeRange(0, atext.length))
atext.addAttribute(NSStrokeWidthAttributeName, value: NSNumber.init(float: -1.0), range: NSMakeRange(0, atext.length))
let titleLabel:UILabel = UILabel.init(frame: CGRectMake(50, 3, 220, 44))
titleLabel.attributedText = atext
titleLabel.textAlignment = NSTextAlignment.Center
titleLabel.font = UIFont(name: "Helvetica", size: 24.0)
}
}
class HomeViewController: BasicViewController {
override func viewDidLoad() {
super.viewDidLoad()
titleString = "My App"
setup()
}
}
If I run this code, I get an empty title. What am I doing wrong? Thanks.
I don't see where you're setting self.navigationItem.titleView = titleLabel

Reduce the code using a method

Hello there I'm doing the Udacity nanodegree, I'm working with textFields, but I need to reduce the code using a method that takes UITextField as parameter.
I actually I don't know what the method could be, and I don't have any idea how to reduce the code.
I will let you the code that and hope you can help me
let memeTextAttributes = [
NSStrokeColorAttributeName : UIColor.blackColor(), NSForegroundColorAttributeName : UIColor.blueColor(), NSFontAttributeName : UIFont(name: "HelveticaNeue-CondensedBlack", size: 40)!, NSStrokeWidthAttributeName : -1.0]
override func viewDidLoad() {
super.viewDidLoad()
topText.defaultTextAttributes = memeTextAttributes
bottomText.defaultTextAttributes = memeTextAttributes
topText.textAlignment = NSTextAlignment.Center
bottomText.textAlignment = NSTextAlignment.Center
shareButton.enabled = false
You can use something like this:
func configTextField(defaultText: String, textField: UITextField)
{
textField.text = defaultText
textField.defaultTextAttributes = memeTextAttributes
textField.autocapitalizationType = .AllCharacters
textField.textAlignment = .Center
textField.delegate = self
}
Since it is a meme you can automatically capitalize the whole text.
Then call the above function in viewDidLoad.
For example, the bottom textField will be called in viewDidLoad by:
configTextField("BOTTOM", textField: bottomTextField)

Resources