If I have this ViewModifier.
struct AppFont: ViewModifier {
var size: Int?
func body(content: Content) -> some View {
content.font(Font.custom("Futura", size: CGFloat(size ?? 15)))
}
}
From that I create an extension one for Text and one for Textfield
extension Text {
func customFont(size : Int) -> some View {
return ModifiedContent(content: self, modifier: AppFont(size: size))
}
}
extension TextField {
func customFont(size : Int) -> some View {
return ModifiedContent(content: self, modifier: AppFont(size: size))
}
}
How can I add this extension to Text, Textfield and Button without repeating the code? So I could use Text("hello").customFont() TextField(...).customFont() or Button().customFont()
There is not really anything to gain by wrapping the font in a custom ViewModifier and then again using it in an extension. I think the best solution is to have an extension on Text separately to have the return type be Text so you can apply other text-specific modifiers to it. You can also have an extension on View that will cover all other scenarios.
extension Text {
func customFont(size : Int = 15) -> Text {
return self.font(Font.custom("Futura",
size: CGFloat(size)))
}
}
extension View {
func customFont(size : Int = 15) -> some View {
return self.font(Font.custom("Futura",
size: CGFloat(size)))
}
}
Related
This is what I mean:
var body: some View {
Text(text)
.font(Font.openSansLight(withSize: 15))
.foregroundColor(Color(uiColor: mode.underlayTextColor))
if scale { //this is a Bool value passed to initializer
.scaledToFill() //Cannot infer contextual base in reference to member 'scaledToFill'
.minimumScaleFactor(0.5)
.lineLimit(1)
}
}
In the example above, you can use the ternary operator in the modifier parameter for .minimumScaleFactor and .lineLimit, e.g.
.minimumScaleFactor(scale ? 0.5 : 1)
.lineLimit(scale ? 1 : nil)
scaledToFill doesn't take a parameter, so you could create you're own modifier that does, e.g.
struct ScaledToFill: ViewModifier {
let scale: Bool
func body(content: Content) -> some View {
if scale {
content.scaledToFill()
} else {
content
}
}
}
extension View {
func scaledToFill(_ scale: Bool) -> some View {
modifier(ScaledToFill(scale: scale))
}
}
then use it like:
.scaledToFill(scale)
Also see this for an example of a "conditional modifier" and why not to use it.
I have a custom View that's basically wrapping Text with some additional functionality. E.g. formatting the text differently based on a value in the environment.
I want to use this custom view in place of a Text, i.e. I want to be able to use modifiers like .bold() on it.
Is it possible to return a Text from a View struct?
Here is 2 ways for you:
Way 1: You can use .bold() just on your CustomView!
struct CustomTextView: View {
#Environment(\.font) var environmentFont: Font?
private let boldAllowed: Bool
let string: String
private init(boldAllowed: Bool, string: String) {
self.boldAllowed = boldAllowed
self.string = string
}
init(_ string: String) {
self.init(boldAllowed: false, string: string)
}
var body: some View {
Text(string)
.font(boldAllowed ? environmentFont?.bold() : environmentFont)
.foregroundColor(Color.red) // <--- ::: some custom work here! :::
}
func bold() -> CustomTextView {
return CustomTextView(boldAllowed: true, string: string)
}
}
use case:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
CustomTextView("Hello, World!")
.bold()
CustomTextView("Hello, World!")
}
.font(Font.title.italic())
}
}
Result:
Way 2: You can use .bold() on any View you want!
I think this is the best possible way, because it is less code and usable on any view!
struct CustomBoldTextViewModifier: ViewModifier {
#Environment(\.font) var environmentFont: Font?
func body(content: Content) -> some View {
return content
.environment(\.font, environmentFont?.bold())
}
}
extension View {
func bold() -> some View {
return self.modifier(CustomBoldTextViewModifier())
}
}
use case:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
}
.bold()
.font(Font.title.italic())
}
}
Result:
You can manually get the view's body. Although this isn't recommended, it is possible. However a better solution may be to pass in a closure to modify your text, see 2nd solution. Both answers achieve the same thing.
Solution #1 (not recommended)
struct ContentView: View {
var body: some View {
CustomText("Custom text")
.getText()
.bold()
.environment(\.font, .largeTitle)
}
}
struct CustomText: View {
private let content: String
init(_ content: String) {
self.content = content
}
var body: some View {
// Apply whatever modifiers from environment, etc.
Text(content)
}
func getText() -> Text {
body as! Text
}
}
Here, the custom text is made bold.
Solution #2 (recommended)
This example you just pass in a closure of how to modify the already modified custom text. This is what I would recommend, plus it looks a lot cleaner.
struct ContentView: View {
var body: some View {
CustomText("Custom text") { text in
text
.bold()
.environment(\.font, .largeTitle)
}
}
}
struct CustomText<Content: View>: View {
private let content: String
private let transform: (Text) -> Content
init(_ content: String, transform: #escaping (Text) -> Content) {
self.content = content
self.transform = transform
}
var body: some View {
// Apply whatever modifiers from environment, etc.
let current = Text(content)
/* ... */
return transform(current)
}
}
i have made a View extension to make fixedSize more flexible.
Here it is.
It works fine, but i am not sure whether there is an easier way to implement this...?
#available(iOS 13.0, *)
struct FixedSizeView<Content> : View where Content : View {
var content: Content
var on: Bool
public init(_ on: Bool, #ViewBuilder content: () -> Content) {
self.content = content()
self.on = on
}
var body : some View {
Group {
if on {
content.fixedSize()
} else {
content
}
}
}
}
#available(iOS 13.0, *)
extension View {
func fixedSize(active: Bool) -> FixedSizeView<Self> {
FixedSizeView(active) {
self
}
}
}
Why don't make it simpler, as this
extension View {
func fixedSize(active: Bool) -> some View {
Group {
if active {
self.fixedSize()
} else {
self
}
}
}
}
Tested & works with Xcode 11.2 / iOS 13.2
#State var modifierEnabled : Bool
struct BlankModifier: ViewModifier {
func body(content: Content) -> some View {
content
}
}
extension View {
func TestModifierView() -> some View{
return self.modifier(BlankModifier())
}
}
How to apply TestModifierView only in case of modifierEnabled == true ?
#available(OSX 11.0, *)
public extension View {
#ViewBuilder
func `if`<Content: View>(_ condition: Bool, content: (Self) -> Content) -> some View {
if condition {
content(self)
} else {
self
}
}
}
#available(OSX 11.0, *)
public extension View {
#ViewBuilder
func `if`<TrueContent: View, FalseContent: View>(_ condition: Bool, ifTrue trueContent: (Self) -> TrueContent, else falseContent: (Self) -> FalseContent) -> some View {
if condition {
trueContent(self)
} else {
falseContent(self)
}
}
}
usage example ( one modifier ) :
Text("some Text")
.if(modifierEnabled) { $0.foregroundColor(.Red) }
usage example2 (two modifier chains related to condition) :
Text("some Text")
.if(modifierEnabled) { $0.foregroundColor(.red) }
else: { $0.foregroundColor(.blue).background(Color.green) }
BUT!!!!!!!!!!!
Important thing that this modifier can be reason of some indentity issues. (later you will understand this)
So in some cases better to use standard if construction
I like the solution without type erasers. It looks strict and elegant.
public extension View {
#ViewBuilder
func modify<TrueContent: View, FalseContent: View>(_ condition: Bool, ifTrue modificationForTrue: (Self) -> TrueContent, ifFalse modificationForFalse: (Self) -> FalseContent) -> some View {
if condition {
modificationForTrue(self)
} else {
modificationForFalse(self)
}
}
}
Usage
HStack {
...
}
.modify(modifierEnabled) { v in
v.font(.title)
} ifFalse: {
$0.background(Color.red) // even shorter
}
If you only plan to apply a modifier (or a chain of modifiers) consider this:
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public extension View {
#ViewBuilder func modifier<VM1: ViewModifier, VM2: ViewModifier>(_ condition: #autoclosure () -> Bool, applyIfTrue: VM1, applyIfFalse: VM2
) -> some View {
if condition() {
self.modifier(applyIfTrue)
} else {
self.modifier(applyIfFalse)
}
}
}
Usage is almost as simple as with regular .modifier.
...
Form {
HStack {
...
}
.modifier(modifierEnabled, applyIfTrue: CornerRotateModifier(amount: 8, anchor: .bottomLeading), applyIfFalse: EmptyModifier())
...
You can omit applyIfFalse part for conciseness and just return self.erase() if condition is false.
I am trying to create ViewModifiers to hold all my type styles in SwiftUI. When I try to add a .fontWeight modifier I get the following error:
Value of type 'some View' has no member 'fontWeight'
Is this possible? Is there a better way to manage type styles in my SwiftUI project?
struct H1: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Color.black)
.font(.system(size: 24))
.fontWeight(.semibold)
}
}
Font has weight as one of it's properties, so instead of applying fontWeight to the text you can apply the weight to the font and then add the font to the text, like this:
struct H1: ViewModifier {
// system font, size 24 and semibold
let font = Font.system(size: 24).weight(.semibold)
func body(content: Content) -> some View {
content
.foregroundColor(Color.black)
.font(font)
}
}
You can achieve this by declaring the function in an extension on Text, like this:
extension Text {
func h1() -> Text {
self
.foregroundColor(Color.black)
.font(.system(size: 24))
.fontWeight(.semibold)
}
}
To use it simply call:
Text("Whatever").h1()
How about something likeā¦
extension Text {
enum Style {
case h1, h2 // etc
}
func style(_ style: Style) -> Text {
switch style {
case .h1:
return
foregroundColor(.black)
.font(.system(size: 24))
.fontWeight(.semibold)
case .h2:
return
foregroundColor(.black)
.font(.system(size: 20))
.fontWeight(.medium)
}
}
}
Then you can call using
Text("Hello, World!").style(.h1) // etc