How to fix the font that cut from the bottom in UIKit? - swift

I have a custom font in the app and I have applied it on the Signup button and it has following result:
As you can see the 'g' has been cut from the bottom. The view is a UIButton with image on it and following properties:
Custom font of size 22.7
View Semantic: Force Right-to-Left
Content Insets: Left:44.3, Right:24.7, Top:22 and Bottom:22
Image Insets: Left only:11.7
I have tried to fix it by providing baselineOffset to value 1 in following code:
#IBDesignable extension UIButton {
#IBInspectable var baselineOffset: CGFloat {
set {
let attributedString = NSMutableAttributedString(string: title)
attributedString.addAttribute(NSAttributedString.Key.baselineOffset, value: newValue, range: NSMakeRange(0, title.count))
self.setAttributedTitle(attributedString, for: .normal)
}
get { 0 }
}
var title: String {
get { self.title(for: .normal) ?? "" }
}
}
It fix the bottom cut but now the title cuts from the top.
Is there any way to add extra space for a title text to draw on the screen?

Related

SwiftUI vertical axis TextFields collapses to nothing when .fixedSize() is applied

iOS 16 (finally) allowed us to specify an axis: in TextField, letting text entry span over multiple lines.
However, I don't want my text field to always fill the available horizontal space. It should fill the amount of space taken up by the text that has been entered into it. To do this, we can apply .fixedSize().
However, using this two things in conjunction causes the text field to completely collapse and take up no space. This bug (?) does not affect a horizontal-scrolling text field.
Is this basic behaviour simply broken, or is there an obtuse but valid reason these methods don't play nice?
This is very simple to replicate:
struct ContentView: View {
#State var enteredText: String = "Test Text"
var body: some View {
TextField("Testing", text: $enteredText, axis: .vertical)
.padding()
.fixedSize()
.border(.red)
}
}
Running this will produce a red box the size of your padding. No text is shown.
I don't want my text field to always fill the available horizontal space. It should fill the amount of space taken up by the text that has been entered into it.
That's a weird wish. If you want to remove the background of the TextField, then do it. But I don't think it's a good idea to have an autosizing TextField. One of the reasons against it is the fact that if you erase all the text then the TextField will collapse to the zero width and you'll never set the cursor into it.
I had exactly the same problem with multiline text. So far the use of axis:.vertical requires a fixed width for the text field. This was for me a major problem when designing a table view where the column width adapts to the widest text field.
I found a very good working solution which I summarised in the following ViewModifier :
struct DynamicMultiLineTextField: ViewModifier {
let minWidth: CGFloat
let maxWidth: CGFloat
let font: UIFont
let text: String
var sizeOfText : CGSize {
get {
let font = self.font
let stringValue = self.text
let attributedString = NSAttributedString(string: stringValue, attributes: [.font: font])
let mySize = attributedString.size()
return CGSize(width: min(self.maxWidth, max(self.minWidth, mySize.width)), height: mySize.height)
}
}
func body(content: Content) -> some View {
content
.frame(minWidth: self.minWidth, idealWidth: self.sizeOfText.width ,maxWidth: self.maxWidth)
}
}
extension View {
func scrollableDynamicWidth(minWidth: CGFloat, maxWidth: CGFloat, font: UIFont, text: String) -> some View {
self.modifier(DynamicMultiLineTextField(minWidth: minWidth, maxWidth: maxWidth, font: font, text: text))
}
Usage (only on a TextField with the option: axis:.vertical):
TextField("Content", text:self.$tableCell.value, axis: .vertical)
.scrollableDynamicWidth(minWidth: 100, maxWidth: 800, font: self.tableCellFont, text: self.tableCell.value)
The text field width changes as you type. If you want to limit the length of a line type "option-return" which starts a new line.
On macOS the situation seems to be a bit more complicated. The problem seems to be that TextField does not extent its rendering surface beyond its initial size. So - when the field grows the text is invisible because not rendered.
I am using the following ViewModifier to force a larger rendering surface. I fear this can be called a "hack":
// For a scrollable TextField
struct DynamicMultiLineTextField: ViewModifier {
let minWidth: CGFloat
let maxWidth: CGFloat
let font: NSFont
#Binding var text: String
#FocusState var isFocused: Bool
#State var firstActivation : Bool = true
#State var backgroundFieldSize: CGSize? = nil
var fieldSize : CGSize {
get {
if let theSize = backgroundFieldSize {
return theSize
}
else {
return self.sizeOfText()
}
}
}
func sizeOfText() -> CGSize {
let font = self.font
let stringValue = self.text
let attributedString = NSAttributedString(string: stringValue, attributes: [.font: font])
let mySize = attributedString.size()
let theSize = CGSize(width: min(self.maxWidth, max(self.minWidth, mySize.width + 5)), height: mySize.height)
return theSize
}
func body(content: Content) -> some View {
content
.frame(width:self.fieldSize.width)
.focused(self.$isFocused)
.onChange(of: self.isFocused, perform: { value in
if value && self.firstActivation {
let oldText = self.text
self.backgroundFieldSize = CGSize(width:self.maxWidth, height:self.sizeOfText().height)
Task() {#MainActor () -> Void in
self.text = "nonsense text nonsense text nonsense text nonsense text nonsense text nonsense text nonsense text nonsense text"
self.firstActivation = false
self.isFocused = false
}
Task() {#MainActor () -> Void in
self.text = oldText
try? await Task.sleep(nanoseconds: 1_000)
self.isFocused = true
self.backgroundFieldSize = nil
Task () {
self.firstActivation = true
}
}
}
})
}
}
extension View {
func scrollableDynamicWidth(minWidth: CGFloat, maxWidth: CGFloat, font: NSFont, text: Binding<String>) -> some View {
self.modifier(DynamicMultiLineTextField(minWidth: minWidth, maxWidth: maxWidth, font: font, text:text))
}
}
Usage:
TextField("Content", text:self.$tableCell.value, axis:.vertical)
.scrollableDynamicWidth(minWidth: 100, maxWidth: 800, font: self.tableCellFont, text: self.$tableCell.value)

How to resize text when app goes full screen

I created a Label (TextField) in swift which resizes when my macOS app goes full screen.
But the text inside the textfield still have same size.It is not resizing.
How to resize text with screen size or with the textfield size?
Please help me with this.
To detect fullscreen changes, add observers in your view controller's viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(didEnterFullscreen), name: NSWindow.didEnterFullScreenNotification, object: self.view.window)
NotificationCenter.default.addObserver(self, selector: #selector(didExitFullscreen), name: NSWindow.didExitFullScreenNotification, object: self.view.window)
Then set the text field's font property in didEnterFullscreen and didExitFullscreen.
However, a much more versatile solution would be to update the font whenever the text field changes size:
class AutoResizableTextField: NSTextField {
override var frame: NSRect {
didSet {
// Find the max font size that does not cause the text to exceed our frame
var font = self.font?.withSize(1) ?? NSFont.systemFont(ofSize: 1)
var string = attributedStringValue
// Increase font size until the text just barely fits
while true {
font = font.withSize(font.pointSize + 1)
// Calculate the size of the text with this font
let next = NSMutableAttributedString(attributedString: string)
next.addAttribute(.font, value: font, range: NSRange(location: 0, length: string.length))
var size = next.size()
// Pad the size a bit to prevent clipping
size.width += 6
size.height += 6
if size.width > frame.width || size.height > frame.height { break }
string = next
}
// Set the font
attributedStringValue = string
}
}
}
Just make sure to set textField.autoresizingMask = [.width, .height] to let the field resize whenever the window resizes.

Swift - Attributed Text features using Storyboard all reset when setting font size programmatically and run simulator

On the storyboard I created a text view. Inserted two paragraphs of text content inside textview. Selected custom attributes on the storyboard and made some words bold. When I run the simulator, everything is ok. But when I set the font size programmatically with respect to the "view.frame.height", the bold words which I set on the storyboard resets to regular words.
Code: "abcTextView.font = abcTextView.font?.withSize(self.view.frame.height * 0.021)"
I couldn't get past this issue. How can I solve this?
The problem is that you're working with an AttributedString. Take a look at Manmal's excellent answer here if you want more context, and an explanation of how the code works:
NSAttributedString, change the font overall BUT keep all other attributes?
Here's an easy application of the extension he provides, to put it in the context of your problem:
class ViewController: UIViewController {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let newString = NSMutableAttributedString(attributedString: myTextView.attributedText)
newString.setFontFace(font: UIFont.systemFont(ofSize: self.view.frame.height * 0.033))
myTextView.attributedText = newString
}
}
extension NSMutableAttributedString {
func setFontFace(font: UIFont, color: UIColor? = nil) {
beginEditing()
self.enumerateAttribute(
.font,
in: NSRange(location: 0, length: self.length)
) { (value, range, stop) in
if let f = value as? UIFont,
let newFontDescriptor = f.fontDescriptor
.withFamily(font.familyName)
.withSymbolicTraits(f.fontDescriptor.symbolicTraits) {
let newFont = UIFont(
descriptor: newFontDescriptor,
size: font.pointSize
)
removeAttribute(.font, range: range)
addAttribute(.font, value: newFont, range: range)
if let color = color {
removeAttribute(
.foregroundColor,
range: range
)
addAttribute(
.foregroundColor,
value: color,
range: range
)
}
}
}
endEditing()
}
}

How set UIButton size by it's title size?

I have UIButton, which title is dynamic changes. Button size should changes with title size and will be equal title size.
How to do this programmatically in Swift?
To have your button use its intrinsic content size and automatically resize based upon its text, use Auto Layout to position the button. Only set constraints to position the button and iOS will use the size of the text to determine the width and height of the button.
For example:
let button = UIButton()
// tell it to NOT use the frame
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Hello", for: .normal)
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
This also works if you create the button in the Storyboard. Again, only give constraints to place the button and it will resize to accommodate the text.
Follow below steps(its not a proper solution but you can solve your problem by doing like this )
Create a UILabel (because UILabel adjust its height and width depends on the text)
UIlabel number of line to 1
Create a UIButton over UILabel
Set button title to ""
Set button's constraint : Align button's top and leading to UILabel and equals width and height
Hope this will works for you :)
You can get UIButton's width and Height dynamically with its title.
With the help of, NSString's Size property we can achieve this.
let buttonNAme = [" hi ", "welcome", "Login", "Forgot Password ??", "New to here. Sign up??"]
var yPos = CGFloat()
override func viewWillAppear(_ animated: Bool) {
yPos = 40
for i in 0..<buttonNAme.count
{
self.view.addSubview(addingCustomButton(buttonTitle: buttonNAme[i], buttonFontSize: 15, buttonCount: i))
}
}
func addingCustomButton(buttonTitle : String, buttonFontSize: CGFloat, buttonCount : Int) -> UIButton
{
let ownButton = UIButton()
ownButton.setTitle(buttonTitle, for: UIControlState.normal)
ownButton.titleLabel?.font = UIFont.systemFont(ofSize: buttonFontSize)
let buttonTitleSize = (buttonTitle as NSString).size(attributes: [NSFontAttributeName : UIFont.boldSystemFont(ofSize: buttonFontSize + 1)])
ownButton.frame.size.height = buttonTitleSize.height * 2
ownButton.frame.size.width = buttonTitleSize.width
ownButton.frame.origin.x = 30
yPos = yPos + (ownButton.frame.size.height) + 10
ownButton.frame.origin.y = yPos
ownButton.tintColor = UIColor.white
ownButton.backgroundColor = .brown
ownButton.tag = buttonCount
ownButton.setTitleColor(UIColor.darkGray, for: UIControlState.highlighted)
ownButton.addTarget(self, action: #selector(ownButtonAction), for: UIControlEvents.touchUpInside)
return ownButton
}
func ownButtonAction(sender: UIButton)
{
print("\n\n Title \(sender.titleLabel?.text) TagNum \(sender.tag)")
}
Output
Just Constraint it's origin, and the size will fit it's button title

Swift How to change UISearchBar clear button color without uploading a new image

I've been looking for an answer for hours now and I still can't figure out how to change an UISearchBar clear button color (the little grey x cross).
All answers explain how to set a new clear button icon but I just want to change its color to white.. even if I change mySearchBar.barStyle to black, the clear button stays grey. I may not have checked on the whole Internet but it just doesn't seem possible.
Thanks
You can use an icon font, draw a string to an image, and use it to set the icon. I've created an extension for this:
extension UISearchBar {
func set_text_image(text: NSString, icon:UISearchBarIcon, attribute:LTDictStrObj? = nil, state:UIControlState = .Normal) {
var textColor: UIColor = UIColor.whiteColor()
var textFont: UIFont = UIFont(name: "FontAwesome", size: 15)!
UIGraphicsBeginImageContext(CGSizeMake(15, 15))
var attr = attribute
if attr == nil {
attr = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
]
}
text.drawInRect(CGRectMake(0, 0, 15, 15), withAttributes: attr)
var image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.setImage(image, forSearchBarIcon:icon, state:state)
}
}
To use it you need to download FontAwesome and add it to your project as a user font, and then call:
search_bar.set_text_image("", icon:.Clear)
search_bar.set_text_image("", icon:.Search)
So that the UISearchBar looks like: https://www.dropbox.com/s/fx7dbtoxd785zo7/Screenshot%202015-05-12%2011.36.02.png?dl=0
To choose an icon, go to the cheatsheet and copy-paste the icon into your source code: http://fortawesome.github.io/Font-Awesome/cheatsheet/
The extension is also a part of LSwift: http://superarts.github.io/LSwift/