Reputation: 115041
I am trying to change the color of a SwiftUI Button
on tvOS.
Modifying the background
almost works, except that you can see that the underlying UIButton
is actually using a rounded, translucent image over the top of the background. This results in a different colour at the corners where the rectangular background lies outside the rounded image.
Adding .padding
emphasises this issue:
struct ContentView: View {
@State
var selected = false
var body: some View {
Button(action: {
self.selected.toggle()
}) {
Text($selected.wrappedValue ? "On":"Off")
.foregroundColor(.white)
}.padding(.all)
.background(self.selected ? Color.green : Color.blue)
}
}
}
A related issue is the changing the color of the "focus" view, as I suspect that this is the same view as the button itself, transformed win size and color.
The typical technique in tvOS with a UIButton
is to change the button image, but there doesn't seem to be any way to access the image of the underlying UIButton
.
Upvotes: 3
Views: 5502
Reputation: 1802
Here is a working solution for tvOS
on Swift 5.5
struct TVButton: View {
// MARK: - Variables
@FocusState private var isFocused: Bool
@State private var buttonScale: CGFloat = 1.0
var title: String?
var bgColor: Color = .white
var action: () -> Void
// MARK: - Body
var body: some View {
TVRepresentableButton(title: title, color: UIColor(bgColor)) {
action()
}
.focused($isFocused)
.onChange(of: isFocused) { newValue in
withAnimation(Animation.linear(duration: 0.2)) {
buttonScale = newValue ? 1.1 : 1.0
}
}
.scaleEffect(buttonScale)
}
}
struct TVRepresentableButton: UIViewRepresentable {
// MARK: - Typealias
typealias UIViewType = UIButton
// MARK: - Variables
var title: String?
var color: UIColor
var action: () -> Void
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setBackgroundImage(UIImage(color: color), for: .normal)
button.setBackgroundImage(UIImage(color: color.withAlphaComponent(0.85)), for: .focused)
button.setBackgroundImage(UIImage(color: color), for: .highlighted)
button.setTitle(title, for: .normal)
button.addAction(.init(handler: { _ in action() }), for: .allEvents)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {}
}
extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
}
now you are able to change Title, background color, scale and handle tap action on tvOS
Upvotes: 1
Reputation: 1387
Try:
button.setBackgroundImage(UIImage.imageWithColor(.lightGray), for: .normal)
You can st it for .focused
and .highlighted
Upvotes: -2
Reputation: 14418
Again, I have only checked it on iOS, but this should help:
struct ContentView: View {
@State var selected = false
var body: some View {
Button(action: { self.selected.toggle() }) {
Text($selected.wrappedValue ? "On":"Off")
.foregroundColor(.white)
} .padding(.all)
.background(RoundedRectangle(cornerRadius: 5.0)
.fill(self.selected ? Color.green : Color.blue))
}
}
You can pass any view into .background() modifier, so you might also want to try placing an Image() in there.
Upvotes: 4
Reputation: 2315
This code seems to work fine on iOS but, as you've shown, in tvOS the underlying UIButton is visible. I'm not sure how to get at the underlying button or its image, but it seems you can hack it for now (until Apple either fixes the issue or provides a way to get at that button.)
First, move the padding up to modify the Text so that it will properly affect the underlying button.
Second, (and this is the hack) clip the view after the background modifier with a cornerRadius. As long as the radius is equal to or greater than that of the underlying button, it will clip off the excess background. (Of course, what you see is not the green Color, but the color resulting from the green color superimposed on the gray of the translucent button image. At least that's how it's shown in Xcode.)
struct ContentView: View {
@State var selected = false
var body: some View {
Button(action: {
self.selected.toggle()
}) {
Text($selected.wrappedValue ? "On":"Off")
.foregroundColor(.white)
.padding(.all)
}
.background(self.selected ? Color.green : Color.blue)
.cornerRadius(5)
}
}
Upvotes: 3