SwiftUI Adding Unnecessary "dot dot dot" to Animated Text - swift

I'm writing an app for iPhone using SwiftUI in XCode.
In one of the views, there is a Text label that changes its text whenever a button is pressed.
The entire view is spring animated, so whenever the text is changed via the button, it is changed with an animation.
The animation works well, except during the animation the Text label adds an unnecessary ellipsis to the end of the text.
I've tried to remove the ellipsis using:
Text("text")
.truncationMode(nil)
However, this gives an error.
Is there any way to .turn off the "..." in the Text label?
If not, is there a way to turn off animations for just that Text label without affecting the others, since the entire view is animated?

You can use minimumScaleFactor(_ factor: CGFloat). The text will shrink according to the factor value.
For example, if your font size is 10 and your factor is 0.4, the text font size will be able to decrease down to 4 if needed.
Text("text")
.minimumScaleFactor(0.1)

You can use Text("text").animation(nil) to turn off animation.
or you can choose other animations to prevent the ...
Text("text").animation(.spring(response: 0.0, dampingFraction:0.2))

Try this:
struct UnAnimatedText: View {
private let text: String
init(_ text: String) {
self.text = text
}
var body: some View {
Button(action: {
}) {
Text(text)
.frame(maxWidth: .infinity)
.animation(nil)
}
.disabled(true)
}
}
The text will change without animation but frame of UnAnimatedText - with animations.
.frame(maxWidth: .infinity) is optional, the main idea is to wrap in Button

Related

MacOS: Commit TextField when clicked outside

From a UX point, the goal is to commit the input when people click anywhere outside the TextField.
For example, if the input is for renaming an item, then when clicked outside, we store the input as the new item name, and replace the TextField with a Text to show the new item name.
I assume it is an expected standard behavior, but please let me know if it is against Apple's MacOS standards.
Is there a standard / conventional way to achieve it with SwiftUI, or with some AppKit workaround?
As I see, onCommit is only triggered when we hit the Return key, not when we click outside the TextField. So what I think what I need to figure out is how to detect clicking outside.
What I considered:
Some built-in view modifier on the TextField which activates this behavior, but I couldn't find any.
Detect focus loss or editingChanged of the TextField, but by default, when we click on the background / a button / a text, the TextField doesn't lose focus, nor is the editingChanged event triggered.
If the TextField is focused, add an overlay on the ContentView with an onTapGesture modifier, but then it would take two taps to trigger a button when a TextField is focused.
Add an onTapGesture modifier on the ContentView, which calls a focusOut method, but it doesn't receive the event when people tap on a child view that also has an onTapGesture on it.
Improving on 4, also call focusOut from the onTapGesture callback of all child views. So far this is the only viable option I see, but I'm not sure it is a good pattern to put extra code in all onTapGestures, just in order to customize TextField behavior.
Example code:
import SwiftUI
#main
struct app: App {
#State private var inputText: String = ""
var body: some Scene {
WindowGroup {
ZStack {
Color.secondary.onTapGesture { print("Background tapped") }
VStack {
Text("Some label")
Button("Some button") { print("Button clicked") }
TextField(
"Item rename input",
text: $inputText,
onCommit: { print("Item rename commit") }
)
}
}
}
}
}

How do i change the font size with still the same font in Swiftui

iam trying to change the font size of an Textlabel so that i still have the same font. Every option i tried also changed the font to the basic one, but i want to use Headline or make the Text fat.
Are you looking for something like this?
import SwiftUI
struct ContentView: View {
#State private var fontSize: CGFloat = 16
var body: some View {
VStack {
Text("Hello, world!")
.font(.system(size: fontSize))
.padding()
Button("Change font") {
fontSize += 2
}
}
}
}
This increases the font size if you click the button. You can, of course, use all kinds of methods to change the fontSize property.
It's not a headline any more, but that is, as far as I understand the apple documentation, a specific font size and font weight for the system font. You can always try the different kind of font weights to mimic the headline thickness.

SwiftUI - Position an overlay relative to its anchor

I have a ZStack containing 2 views:
referenceContent - has some content and a Divider. This is the main content across the screen
popoverContent - is a conditional popup window that only takes up a tiny portion of the screen.
var body: some View {
ZStack {
referenceContent
if popoverCondition {
popoverContent
}
}
}
I want the popoverContent's top edge to line up with the bottom of referenceContent
Anyone know how to make this happen? Or is there just a much better way to view this popup window than I'm doing now? Thanks!
You can do this using the overlay(alignment:content:) modifier (previously overlay(_:alignment:)) in combination with custom alignment guides.
The basic idea is that you align the bottom of your reference view with the top of your popover view.
The annoying thing is that the overlay modifier only lets you specify one alignment guide (for the two views). So if you write stack1.overlay(alignment: .bottom) { stack2 } it will align the bottom of your reference with the bottom of your overlay. A quick way to overcome this is to overwrite the bottom alignment guide of your overlay and return the top instead.
referenceView
.overlay(alignment: .bottom) {
popoverContent
// overwrites bottom alignment of the popover with its top alignment guide.
.alignmentGuide(.bottom) {$0[.top]}
}
Overlay vs ZStack
You might ask: "why don't you use a ZStack instead of an overlay?". Well the difference between the two is that the ZStack will take the size of your popover into consideration when laying out your entire view (reference + popover). That is the opposite of what a popover should do. For a popover, the layout system should only take the size of your reference view into consideration and draw the popover on top of it (without affecting the layout of your reference). That is exactly what the overlay(...) modifier does.
Old API (prior to iOS 15, macOS 12)
In older versions of SwiftUI the arguments of the overlay modifier were in reverse order. So the code example for these older systems is:
referenceView
.overlay(
popoverContent.alignmentGuide(.bottom) {$0[.top]},
alignment: .bottom
)
Custom alignment guides
When you don't want to overwrite an existing alignment guide (because you need it somewhere else for example) you can also use a custom alignment guide. Here is a more generic example using a custom alignment guide named Alignment.TwoSided
extension View {
#available(iOS 15.0, *)
func overlay<Target: View>(align originAlignment: Alignment, to targetAlignment: Alignment, of target: Target) -> some View {
let hGuide = HorizontalAlignment(Alignment.TwoSided.self)
let vGuide = VerticalAlignment(Alignment.TwoSided.self)
return alignmentGuide(hGuide) {$0[originAlignment.horizontal]}
.alignmentGuide(vGuide) {$0[originAlignment.vertical]}
.overlay(alignment: Alignment(horizontal: hGuide, vertical: vGuide)) {
target
.alignmentGuide(hGuide) {$0[targetAlignment.horizontal]}
.alignmentGuide(vGuide) {$0[targetAlignment.vertical]}
}
}
}
extension Alignment {
enum TwoSided: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat { 0 }
}
}
You would use that like this:
referenceView
.overlay(align: .bottom, to: .top, of: popoverContent)

How do you change a Text field .onTapGesture (Swift5, Xcode12.4)?

Seemingly simple but I can't seem to find any results on this.
I have this code
struct ContentView: View {
var MinimalTimer: MinimalistTimer = MinimalistTimer()
var body: some View {
Text("\(MinimalTimer.fourteenMinutes)")
.onTapGesture {
MinimalTimer.decrementTimer()
}
}
}
and I want to tap the timer and display on the text the new time, e.g. 840 -> 839 -> 838.
How do you change the display text on tap?
Timer example
I dont know whether this will work in swiftUI though but try , MinimalTimer.addGestureRecognizer(<#T##gestureRecognizer: UIGestureRecognizer##UIGestureRecognizer#>) there is method something like this , you'll get it with autoComplete
Look for gesture Recogniser

How to get accentColor background for menu items in SwiftUI with reduced transparency?

Using AppKit you can add NSMenuItems to an NSMenu. I saw that there is something similar to NSMenu in SwiftUI, namely MenuButton. But I cannot find any documentation on how it works.
I tried the following:
MenuButton("+") {
Button("New contact") { print("Create new contact") }
Button("New group") { print("Create new group") }
}
And that gives me this
It looks almost OK but when I enable the "Reduce transparency" in system preferences
The buttons have a different background color than the menu (notice the slightly lighter color above and beneath the menu items).
When I hover the menu items, their background color doesn't change like a normal macOS menu. See the image below:
I also tried to change the background color manually using the .background() modifier but that doesn't affect the full width of the menu item.
MenuButton("+") {
Button("New contact") { print("Create new contact") }
.background(Color.accentColor)
Button("New group") { print("Create new group") }
}
I suppose this is because I am placing Buttons inside the MenuButton while it is probably expecting some other SwiftUI element. What elements should I place inside MenuButtons to create a normal looking macOS menu like the one below?
[Update] macOS Big Sur
I also tried this out in Big Sur. While the background renders correctly, in Big Sur, the text color is messed up now. 🤯
I think I found a partial solution to this by configuring a ButtonStyle and applying it to the MenuButton generic structure. Note however that the conditional change of .foregroundColor isn't inherited by the individual's Button()'s Text(). Also the color ain't right.
Perhaps someone wants to improve on this.
struct DetectHover: ButtonStyle {
#State private var hovering: Bool = false
public func makeBody(configuration: DetectHover.Configuration) -> some View {
configuration.label
.foregroundColor(self.hovering ? Color.white : Color.primary)
.background(self.hovering ? Color.blue : Color.clear)
.onHover { hover in
self.hovering = hover
}
}
}
MenuButton(label: Image(nsImage: NSImage(named: NSImage.actionTemplateName)!)) {
// Buttons
}.buttonStyle(DetectHover())
I’ve got something that nearly looks right. I have created a custom button which does change appearance when hovered.
I am very new to both Swift and SwiftUI, so the following may be clumsy. I would welcome any improvements.
struct HoverButton: View {
var text = "Hover Button"
var action = {}
#State private var hovering = false
var body: some View {
Button(text, action: action )
.padding(.horizontal, 6)
.padding(.vertical, 2)
.buttonStyle(PlainButtonStyle())
.frame(minWidth: 0, maxWidth: .infinity)
.background(self.hovering ? Color(.selectedMenuItemColor) : Color(.clear))
.onHover { hover in
self.hovering = hover
}
}
}
// Usage:
MenuButton("Test") {
HoverButton(text: "Apple", action: {
print("Apple")
})
HoverButton(text: "Banana", action: {
print("Banana")
})
}
.font(.system(size: 14, /* weight: .heavy, */ design: .default))
.menuButtonStyle(BorderlessButtonMenuButtonStyle())
The whole point in creating the custom button is to have access to Self so that I can change its appearance. However, there are some serious shortcomings:
The main problem is that the buttons don’t take the full width of the menu body. I have no idea how to fix that.
The menu buttons are centered. I tried using a HStack with a Spacer() but that didn’t help.
I have no idea how to omit the first parameter name as for a real Button