Swiftui 3 API cleanup: is it achievable with custom styles? - swiftui

Swiftui 3 and iOS15 gave us the possibility to use styling more easily, so instead of
.pickerStyle(WheelPickerStyle())
we might use
.pickerStyle(.wheel)
At the same time, we can determine custom styles, for example:
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(Color.blue)
.background(RoundedRectangle(cornerRadius: 12).fill(Color.red))
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
.frame(width: 40, height: 40)
}
}
However, to use the above custom style, I have to write
.buttonStyle(CustomButtonStyle())
How might I achieve the possibility to use the custom style with a simple modifier like this?
.buttonStyle(.custom)

We can create extension to ButtonStyle, like
extension ButtonStyle where Self == CustomButtonStyle {
static var custom: CustomButtonStyle { CustomButtonStyle() }
}
and then use it as
.buttonStyle(.custom)
Tested with Xcode 13 / iOS15
Note: this approach does not work with Xcode 12

Related

SwiftUI: Compact DatePicker with custom design

I need a custom "label" with the behavior of opening the date picker in some kind of popover, even on the iPhone.
When I use the built in DatePicker with compact format, I have the behavior with opening the wheels in a popover, but I cannot customize the look of the "collapsed" version with the gray background (in fact, I can change it to some other color, but not to white e.g., it simply doesn't work).
Another option would be to have a custom Text wrapped inside a Button and then open the DatePicker, but I don't know how to achieve the popover effect on the iPhone. So I guess the first option would be the easier one, but how can I achieve a custom layout for the compact date picker? Nothing super fancy, just something like this:
instead of something like this
its more of a proof of concept, but this might be a way:
import SwiftUI
struct ContentView: View {
#State private var showPicker = true
#State var selectedDate = Date()
Button {
withAnimation {
showPicker.toggle()
}
} label: {
Text(Text(selectedDate.getFormattedDate(format:"HH:mm:")))
.padding()
.padding(.horizontal)
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray)
)
}
.background(
DatePicker("", selection: $selectedDate, displayedComponents: .hourAndMinute)
.datePickerStyle(.wheel)
.frame(width: 200, height: 100)
.clipped()
.background(Color.gray.cornerRadius(10))
.opacity(showPicker ? 1 : 0 )
.offset(x: 50, y: 90)
).onChange(of: selectedDate) { newValue in
withAnimation {
showPicker.toggle()
}
}
}
Note that DidSet or willSet is not possible for Objects, however, we had a nice workaround onChange. When date changed, just toggle, done.

Is there a way to set font tracking (i.e. spacing) inside a custom SwiftUI ButtonStyle?

In SwiftUI you can set font tracking (spacing between letters) on a Text View using the tracking View modifier:
Text("Hello, World!")
.tracking(10)
If you have a Button and apply your custom ButtonStyle you can do all kinds of modifications to the style of the button, but since the configuration.label is not an instance of Text but rather ButtonStyleConfiguration.Label, I don't have direct access to apply tracking. Is there a way to do this without having to set it on the Button's label View directly? It seems like something you ought to be able to do since it's a style-related thing but I don't see how to accomplish it.
public struct MyStyle: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
configuration.label
// (if I tried to put `.tracking` here it wouldn't work because it isn't Text)
.foregroundColor(Color.red)
.clipShape(Capsule())
// … etc.
}
}
ViewModifier to change both font and tracking looks ~ related but no relevant answer.
Yes, ButtonStyleConfiguration.Label is not matched to Text, but it is your style you can ignore standard label, and request own by interface contract, exactly which you need (Text in this case), like in below demo
public struct MyStyle: ButtonStyle {
let label: Text // << here !!
public func makeBody(configuration: Configuration) -> some View {
label // << here !!
.tracking(10)
.foregroundColor(configuration.isPressed ? .black : .red)
.clipShape(Capsule())
}
}
and use it as (or create own button extension with button builder to hide those unneeded curls)
Button(action: {
// action here
}){}
.buttonStyle(MyStyle(label: Text("Hello")))
Tested with Xcode 13.2 / iOS 15.2

SwiftUI: custom view modifier not conforming to ViewModifier?

I am trying to create this modifier:
struct CustomTextBorder: ViewModifier {
func body(content: Content) -> some View {
return content
.font(.largeTitle)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 2)
)
.foregroundColor(.blue)
}
}
When I do, I get Type 'CustomTextBorder' does not conform to protocol 'ViewModifier' error.
It seems like I have to add:
typealias Body = <#type#>
However, I see modifiers being created as I originally did here without having to provide the typealias Body...
This modifier works here:
https://www.simpleswiftguide.com/how-to-make-custom-view-modifiers-in-swiftui/
Why isn't it working for me?
How can I make this modifier work? Why does it work for some and not for others? Does it depend on what the project targets? I am targeting iOS 15.
Without seeing your implementation, it looks like your not initializing the modifier. Be sure you're using the braces at the end CustomTextBorder(). Remember, it's still a function that needs to be called.
Text("SwiftUI Tutorials")
.modifier(CustomTextBorder())
Same if you're making an extension of View
extension View {
func customTextBorder() -> some View {
return self.modifier(CustomTextBorder())
}
}
Your code works fine, but why ViewModifier? you do not need ViewModifier for this simple thing, you can use extension in this way:
struct ContentView: View {
var body: some View {
Text("Hello, World!").customTextBorder
}
}
extension Text {
var customTextBorder: some View {
return self
.font(.largeTitle)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 2)
)
.foregroundColor(.blue)
}
}

Custom ToolTip in swiftUI for iOS

I have a row of colors and I want to display a ToolTip for each of the colors.
struct ColorsView: View {
let colors = [UIColor.red, UIColor.white, UIColor.gray, UIColor.blue, UIColor.black]
var body: some View {
HStack {
ForEach(0..<colors.count) { index in
Color(self.colors[index])
.frame(width: (UIScreen.main.bounds.size.width - 30) / 5, height: 25)
}
}.cornerRadius(12)
}
}
How can I create a custom toolTip for this? I tried wrapping in a ZStack but this doesn't seem to solve the problem exactly. Any help :)
You can also try using this tool: https://github.com/quassummanus/SwiftUI-Tooltip
Here's an example of how you could use it with a simple Text view, you can extrapolate from there. It's very nice because it doesn't rely on any Apple-provided APIs that are not fundamental to SwiftUI, so you can use it on all platforms.
Text("Say something nice...")
.tooltip(.bottom) {
Text("Something nice!")
}
And you get something like this:

SwiftUI - Unable to change color of Image icon

I am trying to build my own custom tab bar view, while building my custom buttons I am unable to change the color of Image().
struct TabBarButton: View {
let title: String
let icon: String
var body: some View {
return GeometryReader{ geometry in
VStack {
Image(self.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width/2, height: CGFloat(25))
.foregroundColor(.white)
Text(self.title)
.font(.system(size: 8))
.foregroundColor(Color.white)
}
}
}
}
I have tried foregroundColor(Color.white), accentColor(Color.white), as well as some different color multipliers. Is there a reason the color isn't anything other than default black? Easy fix is just to get white icons but I was hoping to solve this.
I'm not sure what are you trying to to achieve, but probably you just need template rendering mode, like
Image(self.icon)
.renderingMode(.template)
.foregroundColor(.white)
In assets on import icon (import from *.pdf), set up Image Set → Render as → Template Image
In the code:
Image("ImageFileName")
Or:
You can add the "Template" suffix and set "Rednder as default". In documentation
if the name of the image ends in "Template", use the image as a
template, otherwise render it as the original image
and in code:
Image("ImageFileNameTemplate")
Single Color Images (like icons and symbols)
For setting the color to single-color images with the foregroundColor modifier, you should make sure that image renderingMode is set to template
with code:
Image("Star")
.renderingMode(.template)
.foregroundColor(.yellow)
or
With Assets Catalogue properties:
Note that the image MUST have the alpha channel (like PNG or PDF), otherwise you will get a colored rectangle!
Multi-Color Symbols
From iOS 15, Apple introduced symbols with more rendering mode support. you can create one or change one and export it from the SF Symbols.app:
And you can also use up to 3 colors directly in the code like:
Recolor a Colorful Image!
SwiftUI is so easy and powerful and you can recolor an image in no time with a few modifiers like the blendingMode:
You are making something that should look and behave like button. Why not make it a button right from the start, so you can reuse such kind of buttons wherever you like?
struct TabBarButton: ButtonStyle {
var icon: String = "" // you a free to provide a reasonable default
func makeBody(configuration: Self.Configuration) -> some View {
GeometryReader{ geometry in
VStack {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width/2, height: CGFloat(25)) // I didn't put therse magic numbers here
.foregroundColor(.green) // not very stylish but the color is a at your control
configuration.label
.font(.system(size: 8)) // and here
.foregroundColor(Color.black)
}
}
}
}
Use as usual button with title, action {}.
Button("Tab Bar Button") {
// action code here
}
.buttonStyle(TabBarButton(icon: "info.circle"))
Here is an example of adding custom button to Navigation Bar:
struct TabBarButton: PrimitiveButtonStyle {
var icon: String = "" // you a free to provide a reasonable default
func makeBody(configuration: Self.Configuration) -> some View {
VStack {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.green) // Changes color in NavBar
configuration.label
.foregroundColor(Color.black)
}
}
}
And in ContentView add:
.navigationBarItems(trailing: Button(action: {}, label: {
Text("Some")
}).buttonStyle(TabBarButton(icon: "info.circle")))
The result looks like this:
iOS 15+
Some symbols has multiple layers. You need to use .symbolRenderingMode(.palette) and set the color of each of the layers using .foregroundStyle() view modifier explicitly
Image(systemName: "cloud.sun.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.black, .yellow)
this code will add any color to the image just change the name with color-name
Image("Name")
.resizable()
.foregroundColor(Color.name)
Here the code:
Image("profile_photo").resizable()
.renderingMode(.template)
.foregroundColor(.blue)
.scaledToFill()
.frame(width: 120, height: 120,alignment: .center)
.clipShape(Circle())
you just need template rendering mode. nothing else
Image(your image)
.renderingMode(.template)
.do anything you need to update