SwiftUI preview layout: size that fits does not work - xcode

I want my preview to only have the size of the view and not show the whole iPhone.
I have already tried it with this code:
struct TimerCardView: View {
var body: some View {
Text("Hello, World!")
.padding()
.background(.gray)
}
}
struct TimerCardView_Previews: PreviewProvider {
static var previews: some View {
TimerCardView()
.previewLayout(.sizeThatFits)
}
}
but nothing happened.
What am I doing wrong? I appreciate help

In Xcode 14+, the default mode for the Preview canvas is live mode, which as you’re seeing places your preview into a device screen context.
Manually adjusting the preview layout requires “selectable” mode, which was the default in previous versions of Xcode. You can switch between modes using the palette at the bottom of the toolbar canvas. For example:

Related

How to keep newly opened macOS window in front & prevent being hidden with SwiftUI?

I am using SwiftUI to create a macOS app and need to create a new window that opens with an image inside of it, which I am successfully accomplishing currently.
However, if I click back on the main app window, the newly opened window goes to the background and is hidden (normal behavior), however, I want the newly opened window to always be on top of the main app window AFTER if I click back on the main application window.
The reason is that the new window (WindowGroup) opened contains an image with the information I need to enter in the main app so if it goes behind the main app window, I can't see the image anymore.
Is there a WindowGroup modifier I can implement so that after the WindowGroup("imageView") window opens, it is always on top & how can I integrate into my existing code?
Thank you!
#main
struct customApp: App {
#StateObject var session = SessionStore()
var body: some Scene {
WindowGroup("mainView") {
ContentView().environmentObject(session)
}.handlesExternalEvents(matching: ["mainView"])
WindowGroup("imageView") {
ImageView(url: SessionStore.imageUrl)
}.handlesExternalEvents(matching: ["imageView"])
}
}
View that opens new window
struct ImageViews: View {
#Environment(\.openURL) var openURL
var body: some View {
HStack {
WebImage(string: idUrl)
.onTapGesture {
guard let url = URL(string: "app://imageView") else { return }
openURL(url)
}
}
}
}
Set the window.level to always on top .floating. You can access it via NSApplication.shared.windows.
Button("Window level") {
for window in NSApplication.shared.windows {
window.level = .floating
}
}

SwiftUI is there any way of getting a disabled TextField to be grayed out on Mac OS

When a SwitfUI TextField is disabled, on Mac OS, there is no visual feedback that the field is not enterable (apart from not accepting focus click). I have searched high and low, it looks like simply setting .background(Color.whatever) works for IOS (from all the "how tos" that I have encountered). However for a Mac OS app, it only changes the color of the thin boundary of the textfield. I have futzed around and found that I can add opaque overlays to simulate the effect, but that seems overly complex for what I always took to be conventional standard of greying out of disabled fields. Which makes me think that I am missing something bleedingly obvious somewhere.
Has anyone a sample of a MacOS SwiftUI struct that greys the background of a disabled TextField ? My minimal example of what I am doing to see the issue is below.
struct ContentView: View {
#State var nameEditDisabled = true
#State var myText = "Fred"
var body: some View {
VStack {
Button("Change Name") {
nameEditDisabled.toggle()
}
TextField("hello", text: $myText)
.background(nameEditDisabled ? Color.gray: Color.yellow)
.disabled(nameEditDisabled)
}
}
}
it seems to be "fixed' in swiftUI 3.0, macos 12. I get a slightly darker shade of gray when disabled. When in focus, I get a blue border.
Edit:
struct ContentView: View {
#State var nameEditDisabled = false
#State var myText = "Fred"
var body: some View {
VStack {
Button("Change disabling") {
nameEditDisabled.toggle()
}
TextField("hello", text: $myText)
.colorMultiply(nameEditDisabled ? .gray: .yellow)
.disabled(nameEditDisabled)
}.frame(width: 444, height: 444)
}
}

SwiftUI NSTitlebarAccessoryViewController

I'd like to use the SwiftUI app lifecycle, but my app uses NSTitlebarAccessoryViewController to show a bar of tool options below the toolbar:
Specifically, I'm doing this:
let toolSettingsView = NSHostingView(rootView: ToolAccessoryView(model: model))
let vc = NSTitlebarAccessoryViewController()
vc.view = toolSettingsView
vc.fullScreenMinHeight = accessoryHeight // Ensure tool settings are visible in full screen.
toolSettingsView.frame.size = toolSettingsView.fittingSize
window?.addTitlebarAccessoryViewController(vc)
Is there a (practical) way I can mimic the control appearance (of the sliders, etc.) using pure SwiftUI? When use a SwiftUI view I get this:
Code looks like this:
struct MainView: View {
var model: DataModel
var undoManager: UndoManager
var body: some View {
VStack {
ToolAccessoryView(model: model)
SculptingView(model: model, undoManager: undoManager)
}
}
}
This is implemented as of macOS 13 by using a custom toolbar placement identifier as follows:
extension ToolbarItemPlacement {
static let toolOptionsBar = ToolbarItemPlacement(id: "com.companyname.toolOptions")
}
Then specifying the placement in the .toolbar:
ToolbarItem(placement: .toolOptionsBar) {
ToolAccessoryView()
}
Which in my case looks like this:
The colors on the sliders are a bit odd, which is likely their bug.
See also https://developer.apple.com/documentation/swiftui/toolbarplacement/init(id:) which has some example code.

Animating Text in SwiftUI

SwiftUI has wonderful animation features, but the way it handles changes in Text View content is problematic. It animates the change of the text frame but changes the text immediately without animation. As a result, when the content of a Text View is made longer, animating the transition causes an ellipsis (…) to appear until the text frame reaches its full width. For example, in this little app, pressing the Toggle button switches between shorter and longer text:
Here's the code:
import SwiftUI
struct ContentView: View {
#State var shortString = true
var body: some View {
VStack {
Text(shortString ? "This is short." : "This is considerably longer.").font(.title)
.animation(.easeInOut(duration:1.0))
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The question is: how to avoid the ellipsis? When animating a one character string into a two character string the situation is even worse, because the short string is completely replaced by the ellipsis while it animates into the longer string.
One possibility is to assign a separate id to the view in one state or another by adding the modifier, for instance, .id(self.shortString ? 0 : 1) and then adding a .transition() modifier. That will treat the Text as two different Views, before and after. Unfortunately, in my case I need to move text location during the change, and different ids makes animating that impossible.
I guess the solution is a creative use of AnimatableData. Any ideas?
Here is a demo of possible approach (scratchy - you can redesign it to extension, modifier, or separate view)
Tested with Xcode 11.4 / iOS 13.4
struct ContentView: View {
#State var shortString = true
var body: some View {
VStack {
if shortString {
Text("This is short.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
if !shortString {
Text("This is considerably longer.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
Any suggestions for shrinking an animated gif's dimensions?
I use this way:
- decrease zoom of Preview to 75% (or resize window of Simulator)
- use QuickTimePlayer region-based Screen Recording
- use https://ezgif.com/video-to-gif for converting to GIF
If you add .animation(nil) to the Text object definition then the contents will change directly between values, avoiding ellipsis.
However, this may prevent the animation of the text location, which you also mention wanting to do simultaneously.
You can add one by one character into a string with animation after 0.1 seconds additional, but remember to disable the button toggle while the characters being added, like below:
Code:
public struct TextAnimation: View {
public init(){ }
#State var text: String = ""
#State var toggle = false
public var body: some View {
VStack{
Text(text).animation(.spring())
HStack {
Button {
toggle.toggle()
} label: {
Text("Toggle")
}
}.padding()
}.onChange(of: toggle) { toggle in
if toggle {
text = ""
"This is considerably longer.".enumerated().forEach { index, character in
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) {
text += String(character)
}
}
} else {
text = "This is short."
}
}
}
}

SwiftUI - How to change hierarchical position of View?

Here's a basic example of what doesn't work:
import SwiftUI
struct Test : View {
#State var swapped = false
var body: some View {
if swapped { Color.green }
Color.blue.tapAction {
withAnimation { self.swapped.toggle() }
}
if !swapped { Color.green }
}
}
SwiftUI has no way to figure out that I think of the first Color.green and the second Color.green as the same view, so of course the animation just fades one of them out while fading the other one in at the new location. I'm looking for the way to indicate to SwiftUI that these are the same view, and to animate it to the new location. I discovered the .id() modifier with great excitement because I believed that it would give me the effect that I want:
import SwiftUI
struct Test : View {
#State var swapped = false
var body: some View {
if swapped { Color.green.id("green") }
Color.blue.tapAction {
withAnimation { self.swapped.toggle() }
}
if !swapped { Color.green.id("green") }
}
}
This, unfortunately, does not work either. I'm unbelievably excited about SwiftUI, but it seems to me that the ability to change the structure of the view hierarchy while preserving view identity is quite important. The actual use case which prompted me to think about this is that I have a handful of views which I'm trying to create a fan animation for. The simplest way would be to have the items in a ZStack in one state so that they are all on top of each other, and then to have them in a VStack in the fanned out state, so that they're vertically spread out and all visible. Changing from a ZStack to a VStack of course counts as a change to the structure and therefore all continuity between the states is lost and everything just cross fades. Does anyone know what to do about this?
You can do this with SwiftUI 2 introduced in iOS 14 / macOS 10.16:
#Namespace private var animation
…
MyView.matchedGeometryEffect(id: "myID", in: animation)
I think I have the animation part of your question down, but unfortunately, I don't think it will keep the same instance of the view. I tried taking green out into a variable to see if that works, but if I understand SwiftUI correctly, that doesn't mean the same instance of the view will be shared in two places.
What I'm doing is adding a transition when the green view is added/removed. This way, the view moves to the location of its replacement before disappearing.
struct Test : View {
#State var swapped = false
let green = Color.green
var body: some View {
HStack {
if swapped {
green
.transition(.offset(CGSize(width: 200, height: 0)))
.animation(.basic())
}
Color.blue.animation(.basic()).tapAction {
withAnimation { self.swapped.toggle() }
}
if !swapped {
green.transition(.offset(CGSize(width: -200, height: 0)))
.animation(.basic())
}
}
}
}
This solution is quick-and-dirty and uses hard-coded values based on the iPhone 6/7/8 portrait screen size

Resources