Hi,
I have a code snippet as below that creates a draggable circle when a drag gesture is performed, and the circle disappears when the drag gesture is released. I'm wondering how I can make it detect multiple drag gestures. More specifically, when a finger is performing drag gesture, if another finger presses the screen at this time, a new circle will be generated.
struct ContentView: View {
enum DragState {
case inactive
case active(position: CGPoint)
var location: CGPoint {
switch self {
case .inactive:
return .zero
case .active(let position):
return position
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .active:
return true
}
}
}
@GestureState private var state: DragState = .inactive
var body: some View {
ZStack {
Color.clear.contentShape(Rectangle())
if state.isActive {
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.blue)
.position(state.location)
}
}
.gesture(dragGesture)
}
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0)
.updating($state) { value, state, _ in
state = value.location == .zero ? .inactive : .active(position: value.location)
}
}
}
Post
Replies
Boosts
Views
Activity
Hi,
I have the following code snippet. If I use binding selection in the view model, when I release the swipe gesture, it goes back to "Tab 1", and the "selectedTab" doesn't seem to update automatically. But if I use binding optional selection in the view, then it works as expected. Could someone help me clarify it?
struct TabModel: Identifiable, Hashable {
let id: Int
let name: String
}
class TabViewModel: ObservableObject {
// tabview binding selection
@Published var selectedTab: TabModel?
var tabs: [TabModel]
init() {
tabs = [
TabModel(id: 1, name: "Tab 1"),
TabModel(id: 2, name: "Tab 2"),
TabModel(id: 3, name: "Tab 3")
]
}
}
struct ContentView: View {
@ObservedObject var viewModel: TabViewModel
// tabview binding selection
@State private var selectedTab: TabModel?
var body: some View {
TabView(selection: $viewModel.selectedTab/$selectedTab) {
ForEach(viewModel.tabs) { tab in
Text(tab.name)
.tag(tab)
}
}
.tabViewStyle(.page)
}
}
Hi,
I have a code snippet as below that creates a random selection animation. I would like the animation gradually slows down instead of abruptly stopping when the specified duration is reached. I initially attempted to achieve this by using multiple timers with different intervals to represent various animation speeds and then using another timer to trigger these timers. However, I'm not feeling well with this approach and am seeking better suggestions and insights. Any comments and recommendations would be greatly appreciated.
@State private var timer: Timer?
@State private var elapsedTime: TimeInterval = 0
private let timeInterval: TimeInterval = 0.1
private let duration: TimeInterval = 10
func start() {
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { _ in
if elapsedTime < duration {
elapsedTime += timeInterval
withAnimation(.spring(response: timeInterval)) {
// randomly select an item
}
} else {
timer?.invalidate()
timer = nil
elapsedTime = 0
}
}
timer?.fire()
}
}
Hello,
I am trying to create a simple effect in a sheet view where when a user tap the 'Done' button on keyboard, the 'TextField' should be unfocused and the keyboard should disappear. With almost the same code, the 'Done' button doesn't work as expected in 'SheetView' as it does in 'MainView'. Any suggestions are appreciated.
struct MainView: View {
@State private var text = ""
@FocusState private var isFocused: Bool
@State private var showSheet = false
var body: some View {
VStack {
TextField("text", text: $text)
.focused($isFocused)
.textFieldStyle(.roundedBorder)
Button("showSheetView") {
isFocused = false
showSheet.toggle()
}
}
.padding()
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
isFocused = false
}
}
}
.sheet(isPresented: $showSheet) {
NavigationStack {
SheetView()
}
}
}
}
struct SheetView: View {
@Environment(\.dismiss) var dismiss
@State private var text = ""
@FocusState private var isFocused: Bool
var body: some View {
TextField("text", text: $text)
.focused($isFocused)
.textFieldStyle(.roundedBorder)
.padding()
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Cancel") {
isFocused = false
dismiss()
}
}
// The following tool bar button does not work. It seems tappable, but not functioning.
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
isFocused = false
}
}
}
}
}
// Before I was able to do something like:
struct ContentView: View {
@EnvironmentObject var listData: ListData
var body: some View {
ListView($listData.listDataArray)
}
}
struct ListView: View {
@Binding var listDataArray: [DataType]
}
class ListData: ObservableObject {
@Published var listDataArray: [DataType] = []
}
// Now, I will get the error "Cannot find '$listData' in scope" on the line indicated below when I try to migrate to the Observable macro
struct ContentView: View {
@Environment(ListData.self) var listData
var body: some View {
ListView($listData.listDataArray) ----------> Error
}
}
struct ListView: View {
@Binding var listDataArray: [DataType]
}
@Observable
class ListData {
var listDataArray: [DataType] = []
}
@main
struct SomeApp: App {
@State private var listData = ListData()
var body: some Scene {
WindowGroup {
ContentView
.environment(listData)
}
}
}
The following code snippets are citied from https://www.avanderlee.com/swift/nonisolated-isolated/
actor BankAccountActor {
enum BankError: Error {
case insufficientFunds
}
var balance: Double
init(initialDeposit: Double) {
self.balance = initialDeposit
}
func transfer1(amount: Double, to toAccount: BankAccountActor) async throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
await toAccount.deposit(amount: amount)
}
func transfer2(amount: Double, to toAccount: isolated BankAccountActor) async throws {
guard balance >= amount else { --------- first error
throw BankError.insufficientFunds
}
balance -= amount. -------------------- second error
toAccount.balance += amount
}
func deposit(amount: Double) {
balance = balance + amount
}
}
Please correct me if I am wrong. My understanding of the 'transfer1' method is that it involves two actor instances. The first instance(self) and the second instance(toAccount). The first instance checks its balance and modify it within its isolated context. However, when calling the deposit method of the second instance, the 'transfer1' method of the first instance is non-isolated for the second instance.
By marking transfer1 as async, it becomes an isolated context for the second instance (toAccount), allowing the deposit method to be safely executed within its own actor context.
Additionally, I am confused why two error messages appear on the 'transfer2' method.
1st error: Expression is 'async' but is not marked with 'await'.
2nd error: Actor-isolated property 'balance' can not be mutated on a non-isolated actor instance.
// Before
class A: ObservableObject {
@Published var dataArray: [dataType]
}
class B: ObservableObject {
@Published var dataArray: [dataType]
init(a: A) {
A.$dataArray
.assign(to: &$dataArray)
}
}
// After applying @Observable, I get an error "Cannot find 'A.$anArray' in scope" on the line indicated below.
@Observable
class A {
var dataArray: [dataType]
}
@Observable
class B {
var dataArray: [dataType]
init(a: A) {
A.$dataArray ----------------------------- Error
.assign(to: &$dataArray)
}
}
Class A contains an array to store data fetched from an API. I'm trying to extract some contents from class A into class B. Making them focus on different functionality. All functions in Class B also require the downloaded data.
let number: Double = 1234.56
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
if let formattedString = formatter.string(from: NSNumber(value: number)) {
print(formattedString)
}
Output: ¤1,234.56