I've created a UITextView programmatically, but unfortunately it doesn't behave the way a textview does when you drag it from the attributes inspector to the Main.storyboard.
For example: the text in my programmatically created textview doesn't "break line" at the right side of the textview frame.
Although the documentation mentions textContainerInsets, I can't seem to get it to work. (Xcode doesn't recognise the attribute)
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
var textView:UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.view = self.scrollView
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width;
let screenHeight = screenSize.height;
//Create textview
let textView : UITextField = UITextField(frame : CGRect(x:10, y:(screenHeight/2), width: (screenWidth-20), height: (screenHeight/3) ))
textView.backgroundColor = UIColor( red: 0.9, green: 0.9, blue:0.9, alpha: 1.0 )
textView.placeholder = NSLocalizedString("Start typing...", comment: "")
textView.borderStyle = UITextBorderStyle.Line;
// textView.autocorrectionType = .Yes
self.view.addSubview( textView )
}
Is there any way to make the text inside my textview act like normal? multi line instead of one line?
You're creating a UITextField in your code, not a UITextView.
This:
let textView : UITextField = UITextField(frame : CGRect(x:10, y:(screenHeight/2), width:(screenWidth-20), height: (screenHeight/3) ))
Should be:
let textView : UITextView = UITextView(frame : CGRect(x:10, y:(screenHeight/2), width: (screenWidth-20), height: (screenHeight/3) ))
You also seem to have declared textView as a property in your class, so your probably don't want the let in there:
textView = UITextView(frame : CGRect(x:10, y:(screenHeight/2), width: (screenWidth-20), height: (screenHeight/3) ))
Here's how I would implement your UITextView programatically -
override func viewDidLoad() {
super.viewDidLoad();
self.view.translatesAutoresizingMaskIntoConstraints = false;
let screenSize: CGRect = UIScreen.mainScreen().bounds;
let screenWidth = screenSize.width;
let screenHeight = screenSize.height;
//Create textview
let textView : UITextView = UITextView(frame : CGRect(x: 10,
y: (screenHeight/2),
width: (screenWidth-20),
height: (screenHeight/3)));
textView.backgroundColor = UIColor( red: 0.9, green: 0.9, blue:0.9, alpha: 1.0 );
textView.text = NSLocalizedString("Start typing... maybe...", comment: "");
//textView.borderStyle: border's are not supported by the UITextView
self.view.addSubview(textView);
return;
}
Related
Am not working with storyboards, and below is the full code for my UIViewController for my Main Menu screen. While everything appears to work, I made an error, but don't understand the outcome.
myView, the gray area is set to the safeareaLayout constraints
fillRects is a function where I prefill all the rects for the labels and buttons that I will place on myView
By accident, I passed the wrong view to fillRects, not myView, as intended. Therefore the UILabel I create below is larger than it should be.
But my understanding was that it should have been cropped since it is a child of myView, which is constrained to the safeAreaLayout guide. Yet from the included image, you can see that it goes beyond myView's area on the screen.
Is my error in the way I applied the safeareaLayout guides? Or my understanding as to how they work?
import UIKit
class MainMenuCtrl: UIViewController {
var viewBounds : CGRect = .zero
var topLabelRect : CGRect = .zero
var bottomLabelRect : CGRect = .zero
var menuRect : CGRect = .zero
private let myView : UIView = {
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .gray
return myView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Set background color func
setBGC(vc: view)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.backgroundColor = .green
view.addSubview(myView)
addContraints(main: view, child: myView)
////fill the CGRects for all the labels, and buttons
fillRects(vc: self)
let label = UILabel(frame: self.topLabelRect)
label.textAlignment = .center
label.backgroundColor = .red
label.text = "hello"
label.textColor = nameColor
label.font = .systemFont(ofSize: 40)
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.7
myView.addSubview(label)
}
override var prefersStatusBarHidden: Bool {
return false
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent
}
}
Here is the code for fillRects
func fillRects (vc: MainMenuCtrl) {
vc.viewBounds = vc.view.frame
vc.topLabelRect = CGRect(x: vc.viewBounds.minX, y: vc.viewBounds.minY,
width: vc.viewBounds.width, height: vc.viewBounds.height * 0.05)
vc.bottomLabelRect = CGRect(x: vc.viewBounds.minX, y: vc.viewBounds.height * 0.9,
width: vc.viewBounds.width, height: vc.viewBounds.height * 0.05)
vc.menuRect = CGRect(x: vc.viewBounds.minX, y: vc.viewBounds.height * 0.2,
width: vc.viewBounds.width, height: vc.viewBounds.height * 0.6)
}
A view has a clipToBounds property that dictates whether subViews are restricted to the bounds of their parent view. The default value for this is false, which explains the behaviour you are experiencing.
Setting view.clipToBounds = true on the parent view should result in the sub view behaving as you expected.
This is my first post on StackOverflow! Seems to be an amazing community.
I have created a custom UITextfield with a bottom line (bottomLine) programmatically and also a shadowed line (shadowLine) that I wish should be removed when the user moves away from the textfield. How do I do that?
lazy var textField: UITextField = {
let textField = UITextField()
textField.anchor(width: 150, height: 22)
textField.backgroundColor = UIColor(white: 100, alpha: 1)
var bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: 22, width: 150, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
textField.borderStyle = UITextField.BorderStyle.none
textField.layer.addSublayer(bottomLine)
let shadowLine = UIView()
shadowLine.frame = CGRect(x: 0, y: 22, width: 150, height: 3)
shadowLine.backgroundColor = UIColor.black
shadowLine.layer.shadowColor = UIColor.darkGray.cgColor
shadowLine.layer.shadowOffset = CGSize(width: 3, height: 3)
shadowLine.layer.shadowRadius = 3
shadowLine.layer.shadowOpacity = 1
shadowLine.tag = 1
textField.addSubview(shadowLine)
return textField
}()
Then I implemented the UITextFieldDelegate protocol and one of its optional functions in order to remove shadowLine subview but it doesn't get removed as I want to:
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == textField {
textField.viewWithTag(1)?.removeFromSuperview()
}
}
Can you help a rookie out? :)
It looks like you forgot to set the delegate:
textField.delagate = self
I am using this func inside a UIViewController extension to add a title that adjusts font to fit width.
extesion UIViewController {
func setTitleDifferentSizes(title: String){
self.title = title
guard let navigationBarHeight: CGFloat =
self.navigationController?.navigationBar.frame.height else{
return
}
let tlabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width:
200.0, height: navigationBarHeight))
tlabel.text = self.title
tlabel.textColor = UIColor.white
tlabel.font = font24
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
self.navigationItem.titleView = tlabel
}
}
I took this solution from this SO question and changed it a little bit:
How to resize Title in a navigation bar dynamically
Now the issue I have is that the text of the title is not aligned vertically to the other navigation bar items, as you can see in the images, I show one where I just setup the title without using the above method, and the text there cannot fit but it is aligned properly, and the other image is using the method above where the text fits but it is not aligned.
try this:-
func setTitleDifferentSizes(title: String){
self.title = title
guard let navigationBarHeight: CGFloat =
self.navigationController?.navigationBar.frame.height else{
return
}
let attributedString = NSMutableAttributedString(string: title)
let myAttribute = [ NSForegroundColorAttributeName: UIColor.white ,NSFontAttributeName: font24]
attributedString.addAttributes(myAttribute, range: NSRange(location: 0, length: attributedString.string.characters.count))
attributedString.addAttributes([NSBaselineOffsetAttributeName:6.0], range: NSRange(location: 0, length: title.characters.count)
)
let tlabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width:
200.0, height: navigationBarHeight))
tlabel.attributedText = attributedString
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.minimumScaleFactor = 0.2
tlabel.textAlignment = .center
self.navigationItem.titleView = tlabel
}
if you want to adjust the position of text please change the float value of NSBaselineOffsetAttributeName to set the vertical alignment.
I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView
I am trying to put multiple buttons on an iPhone app with an function, only when I run the function the 2nd time , does the button show. It doesn't show the two buttons which I have configured it to run.
class ViewController: UIViewController {
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
let screensize: CGRect = UIScreen.mainScreen().bounds
override func viewDidLoad() {
super.viewDidLoad()
println(screensize.size.height)
blur()
// add iphone 2 buttons
let rect : CGRect = CGRectMake(100, 50, 200, 50)
var rectObj = NSValue(CGRect: rect)
showButton("Hello", rect: rectObj, redvalue: 1.0, greenvalue: 0.5, bluevalue: 0.5, alphavalue: 1.0)
let rect1 : CGRect = CGRectMake(20, 50, 200, 50)
var rectObj1 = NSValue(CGRect: rect)
showButton("Good bye", rect: rectObj1, redvalue: 0.5, greenvalue: 0.5, bluevalue: 0.5, alphavalue: 1.0)
}
func showButton(title: String, rect: NSValue, redvalue : CGFloat, greenvalue: CGFloat, bluevalue: CGFloat, alphavalue : CGFloat)
{
var rectRestored : CGRect = rect.CGRectValue()
button.frame = CGRectMake(rectRestored.origin.x, rectRestored.origin.y, rectRestored.width, rectRestored.height)
button.backgroundColor = UIColor(red: redvalue , green: greenvalue, blue: bluevalue, alpha: alphavalue)
button.setTitle(title, forState: UIControlState.Normal)
self.view.addSubview(button)
}
func blur()
{
var blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) as UIVisualEffectView
blur.frame = self.view.frame
self.view.addSubview(blur)
}
Does addSubView overwrite it ? If so I have tried insersubview : index and that doesn't seem to work either.
Try to call the method in viewWillAppear insted of viewDidLoad, because in viewDidLoad you still may not have the appropriate frame for main view.