Prior to iOS 16, screen background in a NavigationView used to work very reliably in SwiftUI.
In iOS 16, there is a hollow space (without background color) when navigating back and forth with on-screen-keyboard:
Repro code:
import SwiftUI
struct ContentView: View {
#State private var text: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Edit field", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
NavigationLink(destination: Text("Naviagtion target")) {
HStack {
Image(systemName: "link")
Text("Navigation Link")
}
}
.padding(30)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Any ideas how to fix this?
It does look and feel like a bug, where the safe area that’s adjusted when the keyboard appears doesn’t re-adjust when you navigate back and the keyboard’s not there.
One way round the bug is to adjust the background’s idea of what the safe area is, by telling it to always reclaim the keyboard space:
.background(
Color.gray
.ignoresSafeArea(.keyboard)
)
Because you’re only adjusting the safe area for the background, the rest of your view should continue to behave as before. That means that it’s still elevated to accommodate the (missing) keyboard’s space requirement, but it’s less obvious.
I have a small HStack with Text and a Button. This is limited with maxWidth: 200
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("Some Text")
.frame(maxWidth: .infinity)
Button(action: {}, label: {
Text("A Very much to long button Text with more Text")
.truncationMode(.middle)
})
}.frame(maxWidth: 200)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This ends up scaling the button even beyond the borders of the HStack.
I do not want to hardcode a width with .frame(maxWidth: 50 on the Buttons Label Text.
It should just take as much space as possible, after that truncating the text (.truncationMode)
How can this be done without GeometryReader so that the result looks like:
Update
As suggested, XCode is checked latest 13.2 on macOS 11.6
Seems to be related to known issues with macOS.
Only solution is, to create a custom ButtonStyle and apply the necessary restrictions there:
struct MacOSFixedButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.truncationMode(.middle)
.lineLimit(1)
}
}
extension View {
func fixedMacOSButton(
) -> some View {
self.buttonStyle(
MacOSFixedButtonStyle()
)
}
}
Applied via:
Button("to long to read it and to put it in a view", action: {})
.fixedMacOSButton()
I have an issue with animations within a SwiftUI ScrollView. I can reproduce it in a Playground with the code seen below. I just want to animate the opacity but it also animates the scaling. If I use a VStack instead of a ScrollView it works. But I need it to be scrollable.
Did someone experienced the same issue and could give me a quick hint?
Actual behaviour: https://giphy.com/gifs/h8DSbS1xZ9PJyHIJrY
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
#State var showText = 0.0
var body: some View {
ScrollView {
Text("Test")
.font(.title)
.opacity(showText)
Text("Another really really long text")
.opacity(showText)
}
.frame(width: 320, height: 420)
.background(Color.red)
.onAppear {
withAnimation(Animation.easeInOut(duration: 1)) {
self.showText = 1.0
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
ScrollView {
VStack {
Text("Test")
.font(.title)
Text("Another really really long text")
}
.fixedSize()
.opacity(showText)
}
I am having an issues in swiftui when creating a button or navigationlink using an image as the button. Once I get the code setup the image is blue.
NavigationView {
VStack {
Text("Hello World")
NavigationLink(destination: areaexample()) {
Image("testpic")
.resizable()
.frame(width: 200, height: 200)
.cornerRadius(50)
}
}
}
Some components in iOS automatically apply a caller called tint. If you need to override this behavior, Set the rendering mode of the image to .renderingMode(.original)
Image("testpic").renderingMode(.original)
I'm using SwiftUI on MACOS
If I do this:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
I get this:
and the two grey areas are the ends of a tappable button.
but I would expect the button to be the shape of the blue area.
Any ideas how I can get the whole blue area to be tappable.
(I did look at using .onTapGesture but this doesn't animate the button so that you know you've tapped it.)
You can achieve the look you want by using a ButtonStyle and then specifying colors and other style attributes based on the configuration values being passed in.
It would be nice if there was a happy medium where you could inherit the default button radius, automatic width based on the text length and other attributes, but at least there is the ability to specify all the attributes and get the look you want.
Hope this helps!
import SwiftUI
struct BlueButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? Color.blue : Color.white)
.background(configuration.isPressed ? Color.white : Color.blue)
.cornerRadius(6.0)
.padding()
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World")
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button(action: {
}) {
Text("Press")
.frame(maxWidth: 100, maxHeight: 24)
}
.buttonStyle(BlueButtonStyle())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Inspired by #Gene Z. Ragan 's great answer I've started with that answer and taken this a bit further:
Making the ButtonStyle a bit more flexible:
struct NiceButtonStyle: ButtonStyle {
var foregroundColor: Color
var backgroundColor: Color
var pressedColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.headline)
.padding(10)
.foregroundColor(foregroundColor)
.background(configuration.isPressed ? pressedColor : backgroundColor)
.cornerRadius(5)
}
}
and then some sugar to make it cleaner at the call site:
extension View {
func niceButton(
foregroundColor: Color = .white,
backgroundColor: Color = .gray,
pressedColor: Color = .accentColor
) -> some View {
self.buttonStyle(
NiceButtonStyle(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
pressedColor: pressedColor
)
)
}
}
then means we can use default colouring:
white foreground, grey background and accentedColor pressedColor
Button(action: { } ) {
Text("Button A")
}
.niceButton()
or we can customise the colours:
Button(action: { } ) {
Text("Button B has a long description")
}
.niceButton(
foregroundColor: .blue,
backgroundColor: .yellow,
pressedColor: .orange
)
And we get:
Thanks again Gene.
I raised this with Apple on the Feedback assistant to see if they had any useful thoughts.
Dialogue here:
I said:
If on MACOS with SwiftUI I do this:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
I get a blue box with two grey bits sticking out the sides.
Image attached.
These are the two grey areas are the ends of a tappable button. but I would expect the button to be the shape of the blue area.
The same code works fine as expected on iOS.
(I did look at using .onTapGesture but this doesn't animate the button so that you know you've tapped it.)
Apple said:
iOS and macOS have different default ButtonStyles — iOS is borderless, macOS has that standard bezel effect.
It sounds like you’re trying to create a custom ButtonStyle (and so not have any system provided chrome). So you’ll want to create that, which can apply that blue background to the label of the button, and then apply that to your simple button with Text, e.g.
Button("Press"), action {}).buttonStyle(BluePaddingButtonStyle()
This will ensure that it has the same appearance on every platform you run it on.
I said:
Hi, Thanks for the explanation.
I get what you are saying and I’m ok with the method I’ve come up with.
It still doesn’t seem right that:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
should produce something so odd looking.
I don’t understand why that bit of code couldn’t just produce a blue button.
As I say - it’s not a problem because I’ve worked around it but it
currently doesn’t seem intuitive.
Apple said:
The provided content inside the ViewBuilder is used as the label of the button: not the entire button. The button will still come with the surrounding background, bezel, foreground styling, etc as described by it’s ButtonStyle. So if your button needs to have a very specific appearance, then it needs to customize that style: either to the BorderlessButtonStyle (though note that still does come with a specific foreground appearance style), or to a custom ButtonStyle.
My thoughts:
This did help me understand why it shows as it does but intuitively it still seems wrong !!!
You can "force" an iOS-like behavior on macOS by adding .buttonStyle(.borderless).
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
.buttonStyle(.borderless)
I don't think this is possible but you could try using this
Supports SPM, is build for Swift 5.1 and is lean
In Swift 5, this could be achieved with below simple code -
Button("First Button") {
print("Hello World")
}
.padding(10)
.accentColor(.yellow)
.background(Color.blue)
.cornerRadius(10)