Post

Replies

Boosts

Views

Activity

Reply to Why does adding "if" to "ForEach" break UI?
That is strange. With Xcode 11.4 and macOS 10.15.4 I am getting different results with the if statement (line 12) commented out.Below is a screen-shot showing the app running with the line commented out.Either way, it should work with or without that if statement. If you're result is that it does not work, then the question is still, why?
Mar ’20
Reply to How does Animatable know which property to animate?
Thanks for the response.I've looked at the link you shared, and it does not give me more insight.I could be just really confused, or explaining really badly.animatableData does not return a reference to a property, it only returns a value. As shown in my second example, it uses scaleAsDouble, which is not connected to scale, other than the logic of my code.What if I had another property? What bugs me, is how the framework knows which property it has to interpolate. I've added prints to the getters and setters of scaleAsDoulbe when changing scale, with a .animation modifier.What I DO know: The framework knows about the instance of MyRect. When I change the scale property, it creates another instance of MyRect, and init() is called with this new value. Let's say scale was 3 on the first instance, and 4 on the second instance. The framework now knows that it must call the setter on animatableData with the interpolated values between 3 and 4. After calling the setter, it calls path() to get the new interpolated path. But if I had two properties, and both were changed, then how does the framework know that I am animating scale, and not the other property?What I also did in order to try and understand was to hard-code a value in animatableData that is not the same as the property value. I could see that the framework always calls the getter on animatableData before doing anything else. When the value returned by animatableData was not the same as my property's value, then it would not animate, and the setter would never be called.From this, it looked to me as if the framework was checking which value is returned by animatableData, and matching that to one of the properties of my struct so that it knew which one to animate, but that does not make sense. If two properties had the same starting value, then this would not work, plus it just sounds silly.I hope this helps explain what I am trying to figure out. I am sure I am missing something streight forward, but cannot figure it out.To answer your question about the code that does the animation, this is my test code to animate MyRect:struct ScaledRectView: View { @State private var scale: Int = 100 let possibleScales: [Int] = [10, 20, 50, 100] var body: some View { VStack { MyRect(scale: self.scale) .stroke(Color.blue, lineWidth: 4) .animation(.easeInOut(duration: 1.0)) Text(verbatim: "Scale: \(scale) %") HStack { ForEach(possibleScales, id: \.self) { scale in Button(action: { self.scale = scale }, label: { Text("\(scale) %") }) } } } .padding() } }As you can see, this changes the scale as Integers, but the underlying logic (through animatableData) uses a different value that is of type Double.
Apr ’20
Reply to How do I change a transition based on changed state?
Nope, sorry, that does not make a difference. Adding the || (item.id == 0) means that the first item will always slide up. This does not solve it for two reasons: 1) The first item is not always the top, depending on how far you went down the list; 2) It must not always go in the same direction.You'rs probably looked like it worked because you only went down once, then up again. Have a look at my PreviewProvider. It generated more itmes so that I could go further down the list before going up again.The problem remains: The .transition, with its parameters is added to the Text when it is inserted, or modified. It therefore has the state of the direction at that point. If is is removed because of a change of direction, then it will slide in the wrong direction.I am wondering if there is a way to reset the animation and/or transition when the state changes or delaying it somehow so that the body is re-calculated when the direction changes, and then changed. Any ideas?
Apr ’20
Reply to How do I change a transition based on changed state?
Thanks, this does solve the problem, but it is not as elegant as I have hoped. I am always concerned when I see arbitary timeouts in source code (the 0.1 delay in dispatch - you can change it from 100 ms to 1 ms and it still works). API changes, or even change in CPU load can change the code's behaviour.But it does solve the problem, and also clearly illustrates what is going on, so I've marked it as correct. It would be nice though if there was a way to change the transision in a more deterministic way. I may keep on playing with this one for a while...
Apr ’20
Reply to How do I change a transition based on changed state?
How about this one?The main difference is that I do not have a timed async call. I am trying to more accuritly show what is happening, and trying to solve it.They key issue is that the .transision on the text must be changed when the direction is changed before the text is removed. I did this by changing slideUp to currSlideDirection and adding another state, updatePending. If you press a button to slide up or down, it checks the current direction. If the current direction is not correct, then it only changes the current direction, and set the updatePending flag to indicate that a slide update is pending.When the body is re-calculated (with the correct slide direction being aplied), the code checks if updatePending is set. If so, it clears updatePending, and slides in the current direction. It does this asynchronously (but as soon as it can, without timeout) simply because you cannot change state when the body is being re-calculated.I've often run up against the issue of not being able to change state when the body of a view is being calculated, and had to solve it with more complex solutions. This patern of saving a state to indicate that an update is pending, and then doing it asynchronously may be a clean work around for many other similar problems.struct ContentView: View { private struct DisplayItem { var id: Int var text: String } private enum SlideDirection { case up case down } private let numVisibleItems = 3 @State private var currSlideDirection: SlideDirection = .down @State private var updatePending: Bool = false @State private var startIndex: Int = 0 // var items: [String] = [] var items: [String] = ["Hello", "You", "Boys", "Girls"] var body: some View { if updatePending { // updatePending is set to true when the slide direction changed, but the // slide has not been implemented yet. Change the state (startIndex) // asynchronously so that the state is not changed in the body of the // view: DispatchQueue.main.async { self.updatePending = false self.currSlideDirection == .up ? self.slideUp() : self.slideDown() } } return HStack { VStack { ForEach(visibleItems(), id: \DisplayItem.id) { item in Text("\(item.text)").font(.system(size: 30)).animation(.easeInOut) .transition(.asymmetric( insertion: AnyTransition.opacity.combined(with: .move(edge: self.currSlideDirection == .down ? .top : .bottom )), removal: AnyTransition.opacity.combined(with: .move(edge: self.currSlideDirection == .up ? .top : .bottom )))) } } .animation(.easeInOut(duration: 1.0)) .frame(width: 200, height: 200) VStack { Button(action: { if self.currSlideDirection == .down { // The current slide direction needs to change. This changes the // state, without sliding yet. The state change will change the // transistion on the text above but the slide will only happen // after this. self.updatePending = true self.currSlideDirection = .up } else { // Since we are already sliding in the correct direction, we do not // have to update the transition on the text above, so we can just // slide. self.slideUp() } }, label: { Text("Slide Up") }) Button(action: { if self.currSlideDirection == .up { // See comment at slide up above. self.updatePending = true self.currSlideDirection = .down } else { // See comment at slide up above. self.slideDown() } }, label: { Text("Slide Down") }) } } } func slideUp() { self.startIndex = min(self.items.count-1, self.startIndex + 1) } func slideDown() { self.startIndex = max(0, self.startIndex - 1) } private func visibleItems() -> [DisplayItem] { let endIndex = min(startIndex + numVisibleItems - 1, items.count - 1) var result = [DisplayItem]() for i in startIndex...endIndex { result.append(DisplayItem(id: i, text: items[i])) } return result } }
May ’20
Reply to How to get USB Product String
Thanks, I needed to go up the device tree to find the property that I needed.Sorry, by IOService utility app, I meant IORegistryExplorer. It is one of the XCode developer utilities. It is a GUI version of ioreg. It has good search and filter functions making it much easier to find what you are looking for.
May ’20
Reply to Can I bind to Int but edit with TextField
That is brilliant, thanks, and scales nicely for various different types.It does have a slight side-effect thought that I have not been able to get around. I also cannot see that it is possible to get around.Take the slightly different example below where MyStruct does not contain Ints, but Doubles. When you start off, the text will be "1.2 3.4". As soon as you delete the "4", the text does not change to "1.2 3." as a user would expect, but to "1.2 3.0" because "3." is converted to a valid double.Any ideas of how to get around it?struct MyStruct { var a: Double var b: Double } extension MyStruct { var strValue: String { get { return "\(a) \(b)" } set { let parts = newValue.components(separatedBy: " ") guard parts.count == 2, let aValue = Double(parts[0]), let bValue = Double(parts[1]) else { // Not valid. return } a = aValue b = bValue } } } struct MyStructView: View { @Binding var value: MyStruct var body: some View { TextField("", text: $value.strValue) } } struct ContentView: View { @State var myStruct = MyStruct(a: 1.2, b: 3.4) var body: some View { return VStack { Text("xValue = \(myStruct.a)") Text("yValue = \(myStruct.b)") Divider() MyStructView(value: $myStruct) } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity) } }
May ’20
Reply to How to rotate views in-place when orientation changes?
Hi OOPer,Below is my quickly hacked code to handle rotations. You could use any model that publishes isPortrait to use it as the environmental object.class AppDelegate: UIResponder, ObservableObject, UIApplicationDelegate { @Published var isPotrait = true func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil) return true } @objc func rotated() { self.isPotrait = UIDevice.current.orientation.isPortrait print("isPortrait = \(isPotrait)") } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { } }You also need to add the following (or something similar) to the SceneDelegate where the ContentView is instantiated:// Create the SwiftUI view that provides the window contents. let appDelegate = UIApplication.shared.delegate as! AppDelegate let contentView = ContentView().environmentObject(appDelegate)
May ’20
Reply to Can I bind to Int but edit with TextField
I found a much better solution. Not only does it solve the original problem, but it also solves the problem when editing doubles (see previous comments) and it allows a single value to be edited by multiple TextFields (or sliders, or any other visual element). The key is in not binding the TextField to a string, but creating a new Binding with a getter and setter. A local private string is used to keep the string being edited by the TextField in synch with the value (or partial value in this case). The example below shows a struct with two members. A value of this type can be passed to the custom view that is used to edit it. The value is shown at the top of the view (for information only). The view has two TextFields that is used to edit the two members of the value. This is just a proof of concept and can be improved further by using Formatters. This example also scales nicely in that a custom view can be created to edit a custom type and the complexity can be hidden inside the custom view. struct MyStruct { 	var a: Int 	var b: Double } struct MyStructView: View { 	@Binding var myStruct: MyStruct 	 	@State private var lastValidValue = MyStruct(a: 10, b: 20) 	@State private var isEditingA = false 	@State private var isEditingB = false 	@State private var aStrValue = "" 	@State private var bStrValue = "" 	 	var body: some View { 		 		VStack { 			Text("myStruct.a = \(myStruct.a)	.b = \(myStruct.b)") 			Divider() 			 			HStack { 				Text("a = ") 				TextField("Value a", text: Binding( 				get: { 					if self.isEditingA { 						return self.aStrValue 					} else { 						return "\(self.myStruct.a)" 					} 				}, set: { str in 					self.aStrValue = str 					 					if let tmp = Int(str) { 						self.myStruct.a = tmp 						self.lastValidValue = self.myStruct 					} else { 						self.myStruct.a = self.lastValidValue.a 					} 				}), onEditingChanged: { editing in 					self.isEditingA = editing 				}) 			} 			 			HStack { 				Text("b = ") 				TextField("Value b", text: Binding( 					get: { 						if self.isEditingB { 							return self.bStrValue 						} else { 							return "\(self.myStruct.b)" 						} 				}, set: { str in 					self.bStrValue = str 					 					if let tmp = Double(str) { 						self.myStruct.b = tmp 						self.lastValidValue = self.myStruct 					} else { 						self.myStruct.b = self.lastValidValue.b 					} 				}), onEditingChanged: { editing in 					self.isEditingB = editing 				}) 			} 		} 		.frame(width: 300, height: 100) 		.padding() 		.onAppear { 			self.lastValidValue = self.myStruct 		} 	} } struct ContentView: View { 	@State private var myStruct = MyStruct(a: 1, b: 2.0) 	 	var body: some View { 		MyStructView(myStruct: $myStruct) 	} } struct ContentView2_Previews: PreviewProvider { 	static var previews: some View { 		ContentView() 	} }
Jun ’20
Reply to How do I get a Picker to show initial and selected value?
I found that changing my picker to use an Enum works: Picker("Pick Something: ", selection: $pickedValue) { 	ForEach(TestState.allCases) { state in 		Text(state.rawValue).tag(state) 	} } Why would this work differently from the initial code? TestState is declared: enum TestState: String, CaseIterable, Identifiable {   case one   case two   var id: String { self.rawValue } }
Nov ’20
Reply to How to override Edit>Copy when first responder cannot copy
My app is displaying a default value as the user enters data in the TextField. It is clear that this data that is being displayed will be copied. The problem does not seem to be related to Enabled since it is set in IB. I got it to work by removing the view that had focus (in my case a TextField). When I did this, Copy was enabled and my copy function in code above was called. My problem still seems to be related to providing and telling the system that copy is available when the current first responder cannot handle it. I've tried various things like creating my own NSTextField and trying to override copy but without success. I've also created a new SwiftUI app and posted this thread - https://developer.apple.com/forums/thread/667633 since it more clearly shows my issue. ps. Sorry for the late reply. It seems my notifications are not working.
Nov ’20
Reply to How do I add MenuItem to menubar in macos using SwiftUI?
Thanks, that did help. For completeness, I wrote this small sample app that adds a new menu item to the menu and overrides the copy menu. @main struct MenuTestApp: App {   var body: some Scene {     WindowGroup {       ContentView()     }     .commands {       CommandMenu("My Top Menu") {         Button("Sub Menu Item") { print("You pressed sub menu.") }           .keyboardShortcut("S")       }       CommandGroup(replacing: .pasteboard) {         Button("Cut") { print("Cutting something...") }           .keyboardShortcut("X")         Button("Copy") { print("Copying something...") }           .keyboardShortcut("C")         Button("Paste") { print("Pasting something...") }           .keyboardShortcut("V")         Button("Paste and Match Style") { print("Pasting and Matching something...") }           .keyboardShortcut("V", modifiers: [.command, .option, .shift])         Button("Delete") { print("Deleting something...") }           .keyboardShortcut(.delete)         Button("Select All") { print("Selecting something...") }           .keyboardShortcut("A")       }     }   } }
Feb ’21