Rodrigo Mata
Rodrigo Mata

Reputation: 1861

How to add custom modifier for Text?

So far I am able to write custom modifiers for Views; however, when trying to keep my code DRY, I am trying to add a custom modifier for TextFields. The view modifiers work great with something like so:

struct sampleModifier : ViewModifier {
    var height: CGFloat? = 100
    func body(content: Content) -> some View {
        content
            .frame(height: height)
            .background(Color.white)
            .border(Color.gray, width: 0.5)
            .shadow(color: Color.black, radius: 15, x: 0, y: 10)
    }
}

But when I try to use modifiers like font and so, it shows a lot of errors. I do understand that they might need to be more specific and instead conform to the TextFieldStyleModifier, but I do not get how to make it work. I have tried to do it this way without success:

struct TitleModifier : TextFieldStyleModifier {
    func body(content: TextFieldStyle) -> some View {
        content
            .font(.custom("Open Sans", size: 18))
            .color(Color.green)
            
    }
}

Which obviously fails and shows the following error: Error

If I click on the Fix suggestion, it adds this to my code

TextFieldStyleModifier<<#Style: TextFieldStyle#>>

Which I do not know how to use.

Any suggestions?

Upvotes: 14

Views: 21191

Answers (7)

BytePixelMelody
BytePixelMelody

Reputation: 21

// MARK: - Text Styles, iOS 15+:

import SwiftUI

struct TextStyle {
    var fontFamily: String = "Roboto Flex"
    let fontSize: CGFloat
    let fontWeight: Font.Weight
    let lineHeight: CGFloat
    var lineSpacing: CGFloat { lineHeight - fontSize }
}

// MARK: - Create our styles:

extension TextStyle {
    static let heading1 = TextStyle(
        fontSize: 36,
        fontWeight: .bold,
        lineHeight: 44
    )
    static let heading2 = TextStyle(
        fontSize: 30,
        fontWeight: .medium,
        lineHeight: 32
    )
}

// MARK: - Create ViewModifier:

fileprivate struct TextStyleModifier: ViewModifier {
    let textStyle: TextStyle

    func body(content: Content) -> some View {
        content
            .font(
                Font.custom(
                    textStyle.fontFamily,
                    size: textStyle.fontSize
                )
                .weight(textStyle.fontWeight)
            )
            .lineSpacing(textStyle.lineSpacing)
    }
}

// MARK: - Create View extension:

extension View {
    func setStyle(_ textStyle: TextStyle) -> some View {
        modifier(TextStyleModifier(textStyle: textStyle))
    }
}

// MARK: - Usage:

struct TestFont: View {
    var body: some View {
        VStack(alignment: .leading) {
            Group {
                Text("The quick brown fox jumps over the lazy dog and cat")
                    .setStyle(.heading1)
                Text("The quick brown fox jumps over the lazy dog and cat")
                    .setStyle(.heading2)
            }
            .padding(10)
        }
    }
}

// MARK: - Preview:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        TestFont()
    }
}

Result:

Upvotes: 0

BollMose
BollMose

Reputation: 3498

As I know, use a Text extension is much better. For Text, some modifiers are very special, the return body type is not View, but Text. You can find many here.

 extension Text {
    func font(size: CGFloat) -> Text { // don't return `some View`
        self.font(Font.custom("Open Sans", size: size))
    }
}

This means we defined the type of Content and Body for ViewModifier, not sure there is a better way.

Upvotes: 5

Salvador
Salvador

Reputation: 133

Working with custom fonts:

  • install font in your mac and show font details in font book, use the postscript name all time

postscript name

  • add fonts to app

adding custom fonts

adding custom fonts

add enum, in a new file:

import SwiftUI

enum CustomFonts: String {
        case bold = "Family-Bold", medium = "Family-Medium"
}

add extension, in a new file:

import SwiftUI

extension Font {
    static func custom(_ font: CustomFonts, size: CGFloat) -> SwiftUI.Font {
        SwiftUI.Font.custom(font.rawValue, size: size)
    }
}

use:

Text("Hello World")
  .font(.custom(.bold, size: 24))

Upvotes: 0

Valerika
Valerika

Reputation: 474

Right now it is possible to add .font() and .foregroundColor() modifiers to the content inside ViewModifier. However, if you want to add some custom modifiers that can be applied only to a specific view, for example .strikethrough() modifier for Text View, you can add these modifiers into the extension.

struct TitleModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.custom("Open Sans", size: 18))
            .foregroundColor(.green)
    }
}

extension Text {
    func customText() -> some View {
        self.strikethrough().bold().italic().lineLimit(4)
            .modifier(TitleModifier())
    }
}

Usage Text("Hello").customText().

Upvotes: 24

ozmpai
ozmpai

Reputation: 2864

You can create a custom TextFieldStyle, this will apply only to TextField, not to other views on the view container

struct CustomTextFieldStyle: TextFieldStyle {
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .font(.custom("Open Sans", size: 18))
            .foregroundColor(Color.green)
    }
}

Usage:

Group {
    Text("not applied here")
    TextField("applied here", text: $presenter.name)
}
.textFieldStyle(CustomTextFieldStyle())

Upvotes: 5

Vinoth
Vinoth

Reputation: 9764

In Xcode 11 beta 4, the color(_:) modifier is deprecated. So use the foregroundColor(_:) method instead.

Upvotes: 1

kontiki
kontiki

Reputation: 40639

TextFields are also views, so you create the modifier in the same fashion:

struct TitleModifier : ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.custom("Open Sans", size: 18))
            .foregroundColor(Color.green)

    }
}

Also note that there is no modifier .color(). It is .foregroundColor().

When you want to apply it to a FormField, you just do:

TextField("", text: $field1).modifier(TitleModifier())

Upvotes: 7

Related Questions