var body: some View {
let zstack = ZStack {
Frontside(id: $tangoID, sheet: $showingSheet, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 180.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 0 : 1)
Backside(id: $tangoID, sheet: $showingSheet, bookmark: $bookmarked, results: $showResults, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 0.0 : 180.0), axis: (x: 0.0, y: -1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 1 : 0)
}
.contextMenu(menuItems: {Button(action: {
tangoArray[randomNum].bookmark.toggle()
database.updateUserData(tango: tangoArray[randomNum])
}, label: {
VStack{
Image(systemName: tangoArray[randomNum].bookmark ? "bookmark" : "bookmark.fill")
.font(.title)
Text(tangoArray[randomNum].bookmark ? "Remove bookmark" : "Bookmark")
}
})
})
I'm trying to use a contextMenu to bookmark flashcard items. What I have found though is that even after randonNum has changed, so a different flashcard is presented, the bookmark in the context menu is still showing the previous card's status. For example, it's a filled bookmark if I just tagged the previous card as a bookmark.
Related
I want an NSWindow with fullSizeContentView to take the exact size of a SwiftUI view that has an intrinsic content size. I saw similar posts like this one but they were different in that it was fine to provide a fixed frame at a top level. I don’t want to do that, I want the window size to be exactly the size of the view. How can I do that?
This is a Playground snippet that runs in Xcode 14.1.
import AppKit
import SwiftUI
class MyWindow: NSWindow {
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
print("\(Date().timeIntervalSince1970) setFrame called \(frameRect)")
super.setFrame(frameRect, display: flag)
}
}
let window = MyWindow()
window.styleMask = [
.titled,
.closable,
.resizable,
.fullSizeContentView
]
window.toolbar = nil
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.isMovable = true
window.isMovableByWindowBackground = true
window.standardWindowButton(.closeButton)?.isHidden = false
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
print("\(Date().timeIntervalSince1970) Before content \(window.frame)")
window.contentView = NSHostingView(rootView: ContentView())
print("\(Date().timeIntervalSince1970) After setting content \(window.frame)")
window.makeKeyAndOrderFront(nil)
print("\(Date().timeIntervalSince1970) After makeKeyAndOrderFront \(window.frame)")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("\(Date().timeIntervalSince1970) After 1 second \(window.frame)")
}
struct ContentView: View {
var body: some View {
Text("Hello")
.font(.system(size: 200))
.background(.blue)
.fixedSize()
.ignoresSafeArea()
}
}
The problem is that it leaves some space at the end. Why is this code behaving like that?
It prints this:
1674086812.362426 setFrame called (100.0, 100.0, 100.0, 100.0)
1674086812.363435 Before content (100.0, 100.0, 100.0, 100.0)
1674086812.373186 setFrame called (100.0, -63.0, 431.0, 263.0)
1674086812.3741732 After setting content (100.0, -63.0, 431.0, 263.0)
1674086812.374618 setFrame called (100.0, 85.0, 431.0, 263.0)
1674086812.375651 After makeKeyAndOrderFront (100.0, 85.0, 431.0, 263.0)
1674086812.4359 setFrame called (100.0, 57.0, 431.0, 291.0)
1674086813.41998 After 1 second (198.0, 99.0, 431.0, 291.0)
Why is SwiftUI setting the frame with a different size after showing it?
The problem is your ordering of modifiers. You've put .background before .ignoresSafeArea(), so the background takes the safe area into account, If you re-order as follows, it works as required:
struct ContentView: View {
var body: some View {
Text("Hello")
.font(.system(size: 200))
.fixedSize()
.ignoresSafeArea()
.background(.blue)
}
}
Second attempt
Adding some borders to the views after modifiers, it seems that the text is overlapping the title bar, and is inset that amount from the bottom.
struct ContentView: View {
var body: some View {
Text("Hello")
.font(.system(size: 200))
.fixedSize()
.border(.pink, width: 2)
.ignoresSafeArea()
.border(.yellow, width: 1)
.background(.blue.opacity(0.5))
}
}
Applying a small offset to the View after .ignoresSafeArea()
.ignoresSafeArea()
.offset(y: 0.1)
.background(.blue)
gives:
If you do the same after the .background modifier, the title bar is shown:
.ignoresSafeArea()
.offset(y: 0.1)
.background(.blue)
I don't know why an offset seems to fix the problem.
Ok, after spending a lot of time struggling with this, I think I found a workaround. The idea is to avoid completely ignoreSafeArea which seems buggy. In order to do that, I suppressed safe area behavior on the AppKit side by extending NSHostingView and overriding safe area related behavior. This is the code.
import AppKit
import SwiftUI
class MyWindow: NSWindow {
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
print("\(Date().timeIntervalSince1970) setFrame called \(frameRect)")
super.setFrame(frameRect, display: flag)
}
}
let window = MyWindow()
window.styleMask = [
.titled,
.closable,
.resizable,
.fullSizeContentView
]
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.isMovable = true
window.isMovableByWindowBackground = true
window.standardWindowButton(.closeButton)?.isHidden = false
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
class NSHostingViewSuppressingSafeArea<T : View>: NSHostingView<T> {
required init(rootView: T) {
super.init(rootView: rootView)
addLayoutGuide(layoutGuide)
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
topAnchor.constraint(equalTo: layoutGuide.topAnchor),
trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor)
])
}
private lazy var layoutGuide = NSLayoutGuide()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var safeAreaRect: NSRect {
print ("super.safeAreaRect \(super.safeAreaRect)")
return frame
}
override var safeAreaInsets: NSEdgeInsets {
print ("super.safeAreaInsets \(super.safeAreaInsets)")
return NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
override var safeAreaLayoutGuide: NSLayoutGuide {
print ("super.safeAreaLayoutGuide \(super.safeAreaLayoutGuide)")
return layoutGuide
}
override var additionalSafeAreaInsets: NSEdgeInsets {
get {
print ("super.additionalSafeAreaInsets \(super.additionalSafeAreaInsets)")
return NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
set {
print("additionalSafeAreaInsets.set \(newValue)")
}
}
}
print("\(Date().timeIntervalSince1970) Before content \(window.frame)")
window.contentView = NSHostingViewSuppressingSafeArea(rootView: ContentView())
print("\(Date().timeIntervalSince1970) After setting content \(window.frame)")
window.makeKeyAndOrderFront(nil)
print("\(Date().timeIntervalSince1970) After makeKeyAndOrderFront \(window.frame)")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("\(Date().timeIntervalSince1970) After 1 second \(window.frame)")
}
struct ContentView: View {
var body: some View {
Text("Hello")
.font(.system(size: 200))
.fixedSize()
.background(.blue)
}
}
The result is:
And it prints this:
1675110465.322774 setFrame called (100.0, 100.0, 100.0, 100.0)
1675110465.332483 Before content (100.0, 100.0, 100.0, 100.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110465.3494139 setFrame called (100.0, -35.0, 431.0, 235.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110465.3513222 After setting content (100.0, -35.0, 431.0, 235.0)
1675110465.352477 setFrame called (100.0, 85.0, 431.0, 235.0)
1675110465.3534908 After makeKeyAndOrderFront (100.0, 85.0, 431.0, 235.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
additionalSafeAreaInsets.set NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110466.401649 After 1 second (636.0, 490.0, 431.0, 235.0)
I'm trying to implement a circular progress bar in SwiftUI with an image on the progress and animation. Any help or idea would be appreciated.
Here is what I plan to implement:imageLink
This is what I could implement so far: image2
And this is my code:
struct CircularView: View {
#State var progress:Double = 0
var total:Double = 100
let circleHeight:CGFloat = 217
#State var xPos:CGFloat = 0.0
#State var yPos:CGFloat = 0.0
var body: some View {
let pinHeight = circleHeight * 0.1
VStack {
Circle()
.trim(from: 0.0, to: 0.6)
.stroke(Color(.systemGray5),style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
.frame(width: 278, height: 217, alignment: .center)
.rotationEffect(.init(degrees: 162))
.overlay(
Circle()
.trim(from: 0.0, to: CGFloat(progress) / CGFloat(total))
.stroke(Color.purple,style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
.rotationEffect(.init(degrees: 162))
.rotation3DEffect(
.init(degrees: 1),
axis: (x: 1.0, y: 0.0, z: 0.0)
)
.animation(.easeOut)
.overlay(
Circle()
.frame(width: pinHeight, height: pinHeight)
.foregroundColor(.blue)
.offset(x: 107 - xPos, y: 0 + yPos)
.animation(.easeInOut)
.rotationEffect(.init(degrees: 162))
)
)
.foregroundColor(.red)
Button(action: {
progress += 5
if progress >= 65 {
progress = 0
}
}, label: {
Text("Button")
})
}
}
}
Without changing much of your initial code, the idea is to rotate also the image when the circular progress bar value changes
Your code would look like this:
struct CircularView: View {
#State var progress:Double = 0
var total:Double = 100
let circleHeight:CGFloat = 217
#State var xPos:CGFloat = 0.0
#State var yPos:CGFloat = 0.0
var body: some View {
let pinHeight = circleHeight * 0.1
VStack {
Circle()
.trim(from: 0.0, to: 0.6)
.stroke(Color(.systemGray5),style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
.frame(width: 278, height: 217, alignment: .center)
.rotationEffect(.init(degrees: 162))
.overlay(
Circle()
.trim(from: 0.0, to: CGFloat(progress) / CGFloat(total))
.stroke(Color.purple,style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
.rotationEffect(.init(degrees: 162))
.rotation3DEffect(
.init(degrees: 1),
axis: (x: 1.0, y: 0.0, z: 0.0)
)
// .animation(.easeOut)
.overlay(
Circle()
.frame(width: pinHeight, height: pinHeight)
.foregroundColor(.blue)
.offset(x: 107 - xPos, y: 0 + yPos)
// .animation(.easeInOut)
.rotationEffect(Angle(degrees: progress * 3.6))
.rotationEffect(.init(degrees: 162))
)
)
.foregroundColor(.red)
.animation(.easeOut) // Animation added here
Button(action: {
progress += 5
if progress >= 65 {
progress = 0
}
}, label: {
Text("Button")
})
}
}
}
1. Apply one common animation to have a better transition
I Have removed .animation modifiers in 2 places and place only one on the parent Circle View to allow a smooth animation ( You can still change and adapt it to the output you desire )
2. Calculate Image Angle rotation Degree
Perform simple calculation to determine rotation Angle, how much degree should I rotate the Image view
So, basically it is: .rotationEffect(Angle(degrees: (progress / 60) * (0.6 * 360)) => .rotationEffect(Angle(degrees: progress * 3.6))
How is this problem different from other animation questions: I have not seen any articles addressing reversed Text. Please see below:
Here is the code that causes this:
.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 20.0,
perspective: 1.0
)
Here is how I tried to resolve: :
Tried to replace 180º with 360º which of course made the card spin twice (undesired but rendered the text straight)
Tried to use .reversed() as a workaround
Played with all of the params of 3DEffect to no avail
Searched internet for solution, no answer to this specific problem
Edit Also if anyone knows why the text renders like that when going back to arabic view.. that would be great additional assistance.
Edit Adding example code, able to just copy and paste and go
Edit Another edit, forgot I tried to use .reversed() as a workaround
import SwiftUI
struct ContentView: View {
let grid = [GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center)]
var body: some View {
VStack {
LazyVGrid(columns: grid, content: {
ForEach(Example.example, id: \.name) { ex in
AnimatedView(data: ex)
}
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Example {
let name: String
let translation: String
static let example =
[Example(name: "First", translation: "Second"),
Example(name: "Third", translation: "Fourth"),
Example(name: "Fifth", translation: "Sixth"),
Example(name: "Seventh", translation: "Eigth"),
Example(name: "Ninth", translation: "Tenth"),
Example(name: "Eleventh", translation: "Twelth")
]
}
struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
}.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}
struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
.rotation3DEffect( //<- here
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
) }.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}
When I am using rotation3DEffect some times the vale became 90 or -90 degree which what I planed for that, as soon as my degree hit the 90 or -90, I see a massage in console like this:
ignoring singular matrix: ProjectionTransform(m11: 1.0, m12: 0.0, m13: 0.0, m21: -0.125, m22: -0.25, m23: -0.0025, m31: 12.5, m32: 125.0, m33: 1.25)
Which I know it is because of 90 or -90 degree, I like to know should I be responsive to this massage or simply ignoring it? with this in mind that my app has no issue with getting 90 or -90.
code for recreating the massage:
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 200, alignment: .center)
.rotation3DEffect(
Angle(degrees: 90),
axis: (x: 1.0, y: 0.0, z: 0.0),
anchor: .center,
anchorZ: 0.0,
perspective: 0.5)
}
}
I fixed this by setting z in axis to 0.01
struct Flashcard: View {
#State var tangoID = randomNum
#State var refreshToggle = false
#State var showingSheet = false
#State var bookmarked = tangoArray[randomNum].bookmark
#State public var showingNoMoreCardsSheet = false
#State private var showResults: Bool = false
#State private var fullRotation: Bool = false
var body: some View {
let zstack = ZStack {
Frontside(id: $tangoID, sheet: $showingSheet, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 180.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 0 : 1)
Backside(id: $tangoID, sheet: $showingSheet, bookmark: $bookmarked, results: $showResults, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 0.0 : 180.0), axis: (x: 0.0, y: -1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 1 : 0)
}
.actionSheet(isPresented: $showingSheet) {
ActionSheet(title: Text("Finished 終わり"), message: Text("お疲れさま! But feel free to keep going."), buttons: [.default(Text("はい"))]);
}
.onTapGesture {
self.handleFlipViewTap()
}
.navigationBarTitle("Study")
.contextMenu(menuItems: {Button(action: {
tangoArray[randomNum].bookmark.toggle()
database.updateUserData(tango: tangoArray[randomNum])
self.fullRotation.toggle()
}, label: {
VStack{
Image(systemName: tangoArray[randomNum].bookmark ? "bookmark" : "bookmark.fill")
.font(.title)
Text(tangoArray[randomNum].bookmark ? "Remove bookmark" : "Bookmark")
}
})
})
return zstack
}
private func handleFlipViewTap() -> Void
{
withAnimation(.linear(duration: 0.25))
{
self.showResults.toggle()
}
}
}
public struct Frontside: View
{
#Binding public var id: Int
public var body: some View
{
ZStack{
RoundedRectangle(cornerRadius: 8, style: .continuous)
.frame(width: 140, height: 149)
.zIndex(0)
VStack {
Text(tangoArray[self.id].kanji)
.font(.system(size: 24))
.fontWeight(.regular)
.padding(25)
.lineLimit(3)
.zIndex(1)
Spacer()
}
VStack {
Spacer()
HStack {
Button(action: {
incorrect(i: self.id)
checkAttempts()
self.id = nextCard()
}) {
Image(systemName: "xmark")
.font(.headline)
.opacity(0.4)
}
Button(action: {
correct(i: self.id)
checkAttempts()
self.id = nextCard()
}) {
Image(systemName: "circle")
.font(.headline)
.opacity(0.4)
}
}
}
.zIndex(2)
}
}
}
I have a view which is a flashcard. When the user taps on the incorrect button I want the flash card to slide to the left of the screen, and when the user taps on the correct button I want the flash card to transition/slide to the right of the watch screen. How do I do that?
When the user taps on the incorrect button I want the flash card to
slide to the left of the screen, and when the user taps on the correct
button I want the flash card to transition/slide to the right of the
watch screen
I created a minimum viable example to show you a possible solution. Take a look and tell me if I can help you more.
struct ContentView: View {
private let objWidth = CGFloat(100)
private let objHeight = CGFloat(200)
private let screenWidth = UIScreen.main.bounds.size.width;
private let screenHeight = UIScreen.main.bounds.size.height;
#State private var objOffset = CGFloat(50)
var body: some View {
VStack {
Rectangle()
.frame(width: objWidth, height: objHeight)
.background(Color.black)
.position(x: objOffset, y: (screenHeight-objHeight)/2.0)
Button(action: {
withAnimation{
self.move()
}
}) {
Text("TAP")
}
}
}
private func move() {
if objOffset > screenWidth/2.0 {
objOffset = objWidth/2.0
} else {
objOffset = screenWidth-objWidth/2.0
}
}
}