SwiftUI: Why does a certain Text tracking value truncate the label? - ios

When using a certain combination of text attributes (smallCaps, tracking, weight) in SwiftUI, the label text gets truncated.
I'm just wondering whether this is a bug, or if I'm doing something wrong here.
Please see the following Playground example:
import SwiftUI
import PlaygroundSupport
struct MyView: View {
var body: some View {
VStack {
// This works as expected.
Text("Longer Text")
.font(Font.system(.largeTitle).smallCaps())
.fontWeight(.semibold)
.tracking(15)
// Here, the text gets truncated, even though there is
// a smaller tracking value set, and there would be enough
// space to show the complete text.
Text("Longer Text")
.font(Font.system(.largeTitle).smallCaps())
.fontWeight(.semibold)
.tracking(10)
// This works as expected.
Text("Longer Text")
.font(Font.system(.largeTitle).smallCaps())
.fontWeight(.semibold)
.kerning(15)
// This works as expected.
Text("Longer Text")
.font(Font.system(.largeTitle).smallCaps())
.fontWeight(.semibold)
.kerning(10)
}
}
}
PlaygroundPage.current.setLiveView(MyView())
Result:
I'm using Xcode 11.4, Swift 5.2, and tested in a Playground, on simulators (iOS 13.4), and an iPad (iOS 13.3.1).

Related

SwiftUI Picker iOS 16 not filling available space

I am using the following code (example) to render a SwiftUI Picker on iOS:
let strings: [String] = ["short", "very, ver long string"]
#State var selectedString: String = ""
Form {
Picker("Method", selection: $selectedString) {
ForEach(strings, id: \.self) { string in
Text(string)
}
}
}
In iOS 16 the design of the menu style picker has changed (it now includes 2 small chevrons), which is all good, except it no longer fills the available width (as it did on iOS 15). This results in longer strings flowing onto multiple lines even when this isn't neccessary.
Short String (all fine):
Long String (not so good):
I have tried .fixedSize(), which works to some extend but if the string does in fact need to be on two lines this forces the label to be squished. If I add a background to the Picker, it is clear that it only fills around 1/3 of the available space.
Does anyone have any suggestions?
Separate the label from the Picker and wrap it in a HStack.
Form {
HStack {
// the new label text
Text("Method")
.fixedSize() // give other views in HStack space to grow
// push the external label and Picker to the leading and trailing view edges
Spacer()
Picker("Method", selection: $selectedString) {
ForEach(strings, id: \.self) { string in
Text(string)
}
}
.labelsHidden() // the label is in the Text view
}
}
Hide the Picker label by using the .labelsHidden() modifier.
Use the .fixedSize() modifier on the new Text. This will allow the Picker to expand to fit all its contents.
Use Spacer between Text label and Picker to push both items to the edge.

How to handle text wrapping and alignment / padding with consecutive Text Views in SwiftUI?

When putting Text() views that have enough text to cause it to wrap, one after the other, they don't have the same alignment / padding unless they have a similar word structure.
I would like to both have a solution to this problem that allows for every Text view to have the same alignment and padding, as well as understand the SwiftUI Magic that I am apparently trying to fight.
I believe it has to do with the default "auto centering" alignment behavior of swiftUI but not sure why sentences of similar length don't appear with the same alignment / padding. And when I have tried to apply a group to the text views and apply collective padding or multiLineAlignment etc it doesn't seem to change the output.
struct TutorialView: View {
var body: some View {
VStack {
Text("About This App.")
.padding([.top, .bottom] , 20)
.font(.system(size: 30))
Text("This example text is meant to be long enough to wrap because when it is, then the alignment aint what we thought it would be...")
Text("This is meant to wrap and it will look different than the text above. even though it wraps too")
Text("It isn't always clear which will appear to have padding and which will hug the wall")
Text("Literally all of these are differnt...")
Spacer()
}
}
}
Approach
You are right, default alignment is center
Add different background colors and you will understand how they are aligned.
Code
I have modified your code just to demonstrate the layout
struct ContentView: View {
var body: some View {
VStack {
Text("About This App.")
.padding([.top, .bottom] , 20)
.font(.system(size: 30))
.background(.orange)
VStack(alignment: .leading) {
Text("This example text is meant to be long enough to wrap because when it is, then the alignment aint what we thought it would be...")
.background(.green)
Text("This is meant to wrap and it will look different than the text above. even though it wraps too")
.background(.blue)
Text("It isn't always clear which will appear to have padding and which will hug the wall")
.background(.red)
Text("Literally all of these are differnt...")
.background(.yellow)
Spacer()
}
}
}
}
Note:
There are times .fixedSize(horizontal:, vertical:) would come in handy (not used in this example)

iOS15 bug? Overlapping touch ares on iPhone screens with some HStack()ed Picker() objects

Hello developers!
I encountered a situation, where I like to get some feedback from experienced Swift developers at the pulse of time.
For an iOS app I need some user inputs, three to be precise. These can be seen as a whole number in the current state. So I thought, to put them in an integer variable. I decided to use Swift as the language and SwiftUI for the Interface.
Table of contents:
Story
Situation (Problem)
Question
Hardware
Example Code
THE STORY (in short):
I tried to find a way to get the user input into the variable. TextField() handles String. On TextFields()s tap some screen keyboard appears. After input the user can hit the 'return' button and the keyboard disappears. My goal was intercept all characters that did not belong to an integer. So I added a filter function which only allowed numbers from 0 to 9. Fine, but at the default keyboard the user had to hit the symbols button to get a number pad provided.
So I picked a 'Number Pad'. That one has, despite the unpopulated space, no 'return' button. Further the keyboard blocks the view to the Interface. Well, perhaps implementing something like IQKeyboardManager from 'Iftekhar Qurashi' (see: https://cocoapods.org/pods/IQKeyboardManager) can counter the insufficiency, But I asked myself, if such a basic task requires an external library. There should be a native way, right?
THE SITUATION:
After the described and discarded idea, I looked around a went for some Picker()s, which went well... visually. If you grab a picker wheel which has a neighbor to right, you will move the right neighbors wheel. This is shown is a video (just below 'Step 4:'): https://blckbirds.com/post/swiftui-how-to-create-a-multi-component-picker/
Same on the physical devices when in portrait mode(iPhone 6S, X and 13). Landscape offers enough space.
The suggested method to cure the unwanted behavior, namely to add '.compositingGroup().clipped()' to the end of each Picker()s closure does not work for me (iOS 15). As mentioned in the comments by 'Dave Reed', the fact, that the pickers work fine on my Intel MacBooks Xcode (preview and simulators) could be that im running currently 13.1. over there.
For the screenshot I commented the 'compositingGroup().clipped()' lines as shown in the example code. There are two gray color shades, where I think the touch area is (roughly?) positioned. The same can be said if 'compositingGroup().clipped()' are active at position 1.1 and 2.1 (see comment). If 'compositingGroup().clipped()' get active at position 1.2 and 2.2 the gray bars get cut but the touch area is still wide and overlaps the left neighbor.
See some screenshots:
iPhone13portrait
iPhone13landscape
THE QUESTION:
Is there a way of cutting or stretching the touch area for a Picker() object to fit its current stack?
Are there any ideas of using a number only keyboard with a return button?
THE HARDWARE:
Development Platforms:
MacBook Pro (Retina 13", Early 2015, Dual-Core Intel Core i5), FINE ON SIMULATORs AND PREVIEW (Canvas)(iPhone8, 13), Xcode 13.1
MacBook Pro (16", 2021, Apple M1 Pro), Situation occurs on simulators and Preview (Canvas)(iPhone8, 13), Xcode: now 13.2.1
Hardware Test Devices:
iPhone 6s, iOS Version: 15.2, Situation occurs
iPhone X, iOS Version: 15.2, Situation occurs
iPhone 13, iOS Version: 15.1.1, Situation occurs
THE FULL EXAMPLE CODE (incl. Preview):
// demonstration file for bad multiple picker touch area overlay
import SwiftUI
struct ContentView: View {
#State var rice: Int = 51
#State var corn: Int = 20
let range: ClosedRange<Int> = 0...99
var body: some View {
VStack{
HStack(alignment: .center, spacing: 0){
Spacer()
VStack(){//picker 1
Text("rice")
VStack(){
Picker("", selection: $rice) {
ForEach(range, id: \.self){ Text("\($0)").tag($0) }
}
//POSITION 1.1
.pickerStyle(.wheel)
//.compositingGroup()
//.clipped(antialiased: true)
}
//POSITION 1.2
.frame(width: 40, height: 100)
//.compositingGroup()
//.clipped(antialiased: true)
.border(Color.red)
}
Spacer()
VStack(){//picker 2
Text("corn")
VStack(){
Picker("", selection: $corn) {
ForEach(range, id: \.self){ Text("\($0)").tag($0) }
}
//POSITION 1.2
.pickerStyle(.wheel)
//.compositingGroup()
//.clipped(antialiased: true)
}
//POSITION 2.2
.frame(width: 40, height: 100)
//.compositingGroup()
//.clipped(antialiased: true)
.border(Color(UIColor.secondaryLabel))
}
Spacer()
}// End of HStack
.padding()
}.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 2)
).padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

difference between background color for TextField in SwiftUI between macOS and iOS?

I run into an issue I have no idea how to solve. I plan to have a shared code base for macOS and iOS with SwiftUI (as much as possible).
But already with something simple like setting background color for a text field there is a difference
Following code snippet:
struct NewTextField : View
{
#State var d:String = ""
var body: some View
{
HStack(content:
{
TextField("Date: ", text:$d)
.foregroundColor(.black)
.background(Color.green)
/// ... more views
}
}
}
This is simplified; I have multiple textfields on the real application with background colors depending on the content.
on iOS it looks ok; the full textfield background is set to the desired color; on macOS the TextField appear with white background and colored border.
What I'm doing wrong ?
just to leave it not unanswered:
added
.textFieldStyle(PlainTextFieldStyle())
and it create the desired effect
TextField("Date: ", text:$d)
.textFieldStyle(PlainTextFieldStyle())
.foregroundColor(.black)
.background(Color.green)
based on this post: SwiftUI customized text field in OSX

SwiftUI - unexpected behaviour using onTapGesture with mouse/trackpad on iPadOS and Catalyst

I have in my app a layout showing a list of rectangular cards - each one should be tappable (once) to reveal a set of action buttons and more information, etc.
I have implemented this using .onTapGesture() and I have also put .contentShape(Rectangle() to enforce the tappable area. However, while my implementation works fine for touchscreen interface, when I'm using it with the iPadOS mouse support, and on Catalyst for that matter, I see some very unexpected behaviour.
I've made a minimal reproducible example below that you can copy to recreate the problem.
Where the problems are when using mouse/trackpad input:
Not every click of the mouse is recorded as a tap gesture. This is happening mostly arbitrary except from in a few cases:
It seems to click either only in very specific areas, or when clicking multiple times in the same spot.
It seems that in a lot of cases only every other click is recorded. So I double click to get only one tap gesture.
It isn't evident in this example code, but in my main app the tappable areas are seemingly arbitrary - you can usually click near text or in alignment with it to record a tap gesture, but not always.
If you are running the example code you should be able to see the problem by repeatedly moving the mouse and attempting one click. It doesn't work unless you click multiple times in the same spot.
What does work as expected:
All input using touch instead of mouse; regardless of where you tap it records a tap gesture.
Mouse input when running as a native Mac target. The issues mentioned above are only for mouse/trackpad when running the example under iPadOS and Mac Catalyst.
Code I used to recreate this problem (has a counter to count every time a tap gesture is recorded):
struct WidgetCompactTaskItemView: View {
let title: String
let description: String
var body: some View {
HStack {
Rectangle()
.fill(Color.purple)
.frame(maxWidth: 14, maxHeight: .infinity)
VStack(alignment: .leading) {
Text(title).font(.system(size: 14, weight: .bold, design: .rounded))
Text(description).font(.system(.footnote, design: .rounded))
.frame(maxHeight: .infinity)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(1)
.padding(.vertical, 0.1)
Spacer()
}
.padding(.horizontal, 6)
.padding(.top, 12)
}
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .leading)
.background(Color.black)
.cornerRadius(16)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 0.5)
)
}
}
struct ContentView: View {
#State var tapCounter = 0
var body: some View {
VStack {
Text("Button tapped \(tapCounter) times.")
WidgetCompactTaskItemView(title: "Example", description: "Description")
.contentShape(Rectangle())
.onTapGesture(count: 1) {
tapCounter += 1
}
Spacer()
}
}
}
I have tried several things including moving modifiers around, setting eoFill to true on the contentShape modifier (which didn't fix the problem but simply made different unexpected behaviour).
Any help to find a solution that works as expected and works consistently whether mouse or touch would be much appreciated. I am not sure if I am doing something wrong or if there is a bug here, so please try and recreate this example yourself using the code to see if you can reproduce the problem.
So I realised that there was a much better solution that could bypass all the oddities that .onTapGesture had for me with mouse input. It was to encapsulate the whole view in a Button instead.
I made this into a modifier similar to onTapGesture so that it's much more practical.
import Foundation
import SwiftUI
public struct UltraPlainButtonStyle: ButtonStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct Tappable: ViewModifier {
let action: () -> ()
func body(content: Content) -> some View {
Button(action: self.action) {
content
}
.buttonStyle(UltraPlainButtonStyle())
}
}
extension View {
func tappable(do action: #escaping () -> ()) -> some View {
self.modifier(Tappable(action: action))
}
}
Going through this:
I first have a button style which simply returns the label as is. This is necessary because the default PlainButtonStyle() still has a visible effect when clicked.
I then create a modifier that encapsulates the content given in a Button with this button style, then add that as an extension to View.
Usage example
WidgetCompactTaskItemView(title: "Example", description: "Description")
.tappable {
tapCounter += 1
}
This has solved all problems I've been having with clickable area using a mouse.

Resources