![](/img/trans.png)
[英]Image Picker in SwiftUI - how do I know when the user has chosen an image in the hosting UIkit based ViewController
[英]In SwiftUI, how do I know when a Picker selection was changed? Why doesn't didSet work?
I have a Picker
in a SwiftUI Form
, and I'm trying to process the value whenever the picker changes. 我希望能夠在表示當前選定值的變量上使用didSet
來執行此操作。
import SwiftUI
struct ContentView: View {
enum TransmissionType: Int {
case automatic
case manual
}
@State private var selectedTransmissionType: Int = TransmissionType.automatic.rawValue {
didSet {
print("selection changed to \(selectedTransmissionType)")
}
}
private let transmissionTypes: [String] = ["Automatic", "Manual"]
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedTransmissionType,
label: Text("Transmission Type")) {
ForEach(0 ..< transmissionTypes.count) {
Text(self.transmissionTypes[$0])
}
}
}
}
}
}
}
Picker
UI(大部分)按預期工作:我可以看到選擇了默認值,點擊選擇器,它會打開一個新視圖,我可以 select 另一個值,然后它返回主窗體並向我顯示新值被選中。 但是,從不調用didSet
。
我看到了這個問題,但在我的View
代碼中添加更多內容而不是在變量更改時僅處理新值(如果可能的話)對我來說似乎很奇怪。 即使會導致更復雜的視圖,使用onReceive
會更好嗎? 我的主要問題是:阻止didSet
的代碼有什么問題?
我用這個例子來達到這一點。
除了我的常規問題之外,我還有一些關於這個例子的其他問題:
A)我有一個enum
和一個Array
來表示相同的兩個值,這似乎很奇怪。 有人還可以建議一種更好的方法來構建它以避免這種冗余嗎? 我考慮了一個TransmissionType
object,但與enum
相比,這似乎有點矯枉過正......也許不是?
B)當點擊選擇器時,帶有選擇器選項的屏幕會滑過,然后兩個選項會向上跳躍一點。 這讓人感覺刺耳、緊張和糟糕的用戶體驗。 我在這里做錯了什么導致糟糕的用戶體驗嗎? 或者它可能是一個 SwiftUI 錯誤? 每次更改選擇器時都會出現此錯誤:
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (eg table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window.
我早些時候開始輸入這個,然后回來發現 LuLuGaGa 已經打敗了我。 :D 但既然我有這個反正......
主要問題:來自Swift 語言指南:
“當您為存儲的屬性分配默認值,或在初始化程序中設置其初始值時,該屬性的值會直接設置,而無需調用任何屬性觀察者。”
因此,在構造視圖時,屬性觀察者不會觸發。 但是當@State
變量發生變化時,會構造一個新的視圖實例(請記住,視圖是結構或值類型)。 因此, didSet
屬性觀察器實際上對@State
屬性沒有用處。
您要做的是創建一個符合ObservableObject
的 class ,並使用@ObservedObject
屬性包裝器從您的視圖中引用它。 因為 class 存在於結構之外,所以您可以在其屬性上設置屬性觀察器,它們會像您期望的那樣觸發。
問題 A:如果你使它符合CaseIterable
,你可以只使用枚舉(見下面的例子)
問題 B:據我所知,這似乎是一個 SwiftUI 錯誤,因為它發生在NavigationView
/ Form
組合內的任何Picker
上。 我建議向 Apple 報告。
以下是我如何刪除枚舉和數組的冗余,並將選擇保存在 UserDefaults 中:
extension ContentView {
// CaseIterable lets us use .allCases property in ForEach
enum TransmissionType: String, CaseIterable, Identifiable, CustomStringConvertible {
case automatic
case manual
// This lets us omit 'id' parameter in ForEach
var id: TransmissionType {
self
}
// This just capitalizes the first letter for prettier printing
var description: String {
rawValue.prefix(1).uppercased() + rawValue.dropFirst()
}
}
class SelectionModel: ObservableObject {
// Save selected type to UserDefaults on change
@Published var selectedTransmissionType: TransmissionType {
didSet {
UserDefaults.standard.set(selectedTransmissionType.rawValue, forKey: "TransmissionType")
}
}
// Load selected type from UserDefaults on initialization
init() {
if let rawValue = UserDefaults.standard.string(forKey: "TransmissionType") {
if let transmissionType = TransmissionType(rawValue: rawValue) {
self.selectedTransmissionType = transmissionType
return
}
}
// Couldn't load from UserDefaults
self.selectedTransmissionType = .automatic
}
}
}
然后你的觀點看起來像
struct ContentView: View {
@ObservedObject var model = SelectionModel()
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $model.selectedTransmissionType, label: Text("Transmission Type")) {
ForEach(TransmissionType.allCases) { type in
Text(type.description)
}
}
}
}
}
}
}
這里有兩個問題。
1)如果標題樣式為“.inline”,則可以解決跳躍問題。
2) OnReceive() 是用組合框架方法替換didSet
請求的最簡單方法之一,它是SwiftUI 的核心技術。
struct ContentView: View {
enum TransmissionType: Int {
case automatic
case manual
}
@State private var selectedTransmissionType: Int = TransmissionType.automatic.rawValue {
didSet {
print("selection changed to \(selectedTransmissionType)")
}
}
private let transmissionTypes: [String] = ["Automatic", "Manual"]
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedTransmissionType,
label: Text("Transmission Type")) {
ForEach(0 ..< transmissionTypes.count) {
Text(self.transmissionTypes[$0])
}
}
}
}.navigationBarTitle("Title", displayMode: .inline) // this solves jumping and all warnings.
}.onReceive(Just(selectedTransmissionType)) { value in
print(value) // Just one step can monitor the @state value.
}
}
}
didSet 不會在 @State 上被調用,因為它是一個包裝器 - 您在 state 而不是 state 本身中設置一個值。 重要的問題是你為什么想知道它已經設置好?
您的視圖可以簡化一點:
如果您將枚舉聲明為具有原始類型的字符串,則不需要名稱數組。 如果您將其聲明為 CaseIterable,您將能夠通過調用 Array(TransmissionType.allCases) 來獲取所有案例。 如果它也被聲明為可識別,您將能夠將所有案例直接傳遞到 ForEach。 接下來,您需要將 rawValue 傳遞到文本中,並記住在其上放置標簽,以便可以進行選擇:
struct ContentView: View {
enum TransmissionType: String, CaseIterable, Identifiable {
case automatic
case manual
var id: String {
return self.rawValue
}
}
@State private var selectedTransmissionType = TransmissionType.automatic
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedTransmissionType,
label: Text("Transmission Type")) {
ForEach(Array(TransmissionType.allCases)) {
Text($0.rawValue).tag($0)
}
}
}
}
}
}
}
我看不出奇怪的跳躍從何而來——你是否也在設備上復制了它?
嘿,沒有閱讀全文,但如果您想知道選擇器的更改值是什么。 只需使用.onChange(of: YourPicker) { changedValue in }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.