我正在尝试在SwiftUI中编写“单视图应用程序”。主要设计非常简单。我有一个要在NavigationView->列表中的主视图中显示的项目列表(例如费用)。
列出视图源代码
import SwiftUI
struct AmountBasedModifier : ViewModifier{
var amount: Int
func body(content: Content) -> some View {
if amount <= 10{
return content.foregroundColor(Color.green)
}
else if amount <= 100{
return content.foregroundColor(Color.blue)
}
else {
return content.foregroundColor(Color.red)
}
}
}
extension View {
func amountBasedStyle(amount: Int) -> some View {
self.modifier(AmountBasedModifier(amount: amount))
}
}
struct ExpenseItem: Identifiable, Codable {
var id = UUID()
var name: String
var type: String
var amount: Int
static var Empty: ExpenseItem{
return ExpenseItem(name: "", type: "", amount: 0)
}
}
class Expenses: ObservableObject {
@Published var items = [ExpenseItem](){
didSet{
let encoder = JSONEncoder()
if let data = try? encoder.encode(items){
UserDefaults.standard.set(data, forKey: "items")
}
}
}
init() {
let decoder = JSONDecoder()
if let data = UserDefaults.standard.data(forKey: "items"){
if let items = try? decoder.decode([ExpenseItem].self, from: data){
self.items = items
return
}
}
items = []
}
}
struct ContentView: View {
@ObservedObject var expenses = Expenses()
@State private var isShowingAddNewItemView = false
var body: some View {
NavigationView{
List{
ForEach(self.expenses.items) { item in
NavigationLink(destination: ExpenseItemHost(item: item, expenses: self.expenses)){
HStack{
VStack(alignment: .leading){
Text(item.name)
.font(.headline)
Text(item.type)
.font(.subheadline)
}
Spacer()
Text("$\(item.amount)")
.amountBasedStyle(amount: item.amount)
}
}
}.onDelete(perform: removeItems)
}
.navigationBarTitle("iExpense")
.navigationBarItems(leading: EditButton(), trailing: Button(action:
{
self.isShowingAddNewItemView.toggle()
}, label: {
Image(systemName: "plus")
}))
.sheet(isPresented: $isShowingAddNewItemView) {
AddNewExpense(expenses: self.expenses)
}
}
}
func removeItems(at offsets: IndexSet){
self.expenses.items.remove(atOffsets: offsets)
}
}
每个行项目都是NavigationLink,以只读模式打开费用,显示费用项目的所有属性。
右上角有一个添加按钮,使用户可以在列表中添加新的费用项目。 AddNewExpenseView(显示为工作表)可以访问列表数据源。因此,每当用户添加新费用时,列表的数据源就会更新(通过添加新项目),并且工作表将被撤消。
添加查看源代码
struct AddNewExpense: View {
@ObservedObject var expenses: Expenses
@Environment(\.presentationMode) var presentationMode
@State private var name = ""
@State private var type = "Personal"
@State private var amount = ""
@State private var isShowingAlert = false
static private let expenseTypes = ["Personal", "Business"]
var body: some View {
NavigationView{
Form{
TextField("Name", text: $name)
Picker("Expense Type", selection: $type) {
ForEach(Self.expenseTypes, id: \.self) {
Text($0)
}
}
TextField("Amount", text: $amount)
}.navigationBarTitle("Add New Expense", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
if let amount = Int(self.amount){
let expenseItem = ExpenseItem(name: self.name, type: self.type, amount: amount)
self.expenses.items.append(expenseItem)
self.presentationMode.wrappedValue.dismiss()
}else{
self.isShowingAlert.toggle()
}
}, label: {
Text("Save")
}))
.alert(isPresented: $isShowingAlert) {
Alert.init(title: Text("Invalid Amount"), message: Text("The amount should only be numbers and without decimals"), dismissButton: .default(Text("OK")))
}
}
}
}
费用明细(只读)查看源代码
struct ExpenseItemView: View {
var item: ExpenseItem
var body: some View {
List{
Section{
Text("Name")
.font(.headline)
Text(item.name)
}
Section{
Text("Expense Type")
.font(.headline)
Text(item.type)
}
Section{
Text("Amount")
.font(.headline)
Text("$\(item.amount)")
}
}.listStyle(GroupedListStyle())
.navigationBarTitle(Text("Expense Details"), displayMode: .inline)
}
}
到目前为止,一切都很好。然后,我想到在ExpenseItem View屏幕上添加一个Edit按钮,以便用户可以编辑Expense。我创建了一个编辑视图,当单击“编辑”按钮时,该视图将作为“只读”视图的工作表启动。
修改查看代码
struct ExpenseItemHost: View {
@State var isShowingEditSheet = false
@State var item: ExpenseItem
@State var itemUnderEdit = ExpenseItem.Empty
var expenses: Expenses
var body: some View {
VStack{
ExpenseItemView(item: self.item)
}
.navigationBarItems(trailing: Button("Edit")
{
self.isShowingEditSheet.toggle()
})
.sheet(isPresented: $isShowingEditSheet) {
EditExpenseItemView(item: self.$itemUnderEdit)
.onAppear(){
self.itemUnderEdit = self.item
}
.onDisappear(){
//TO DO: Handle the logic where save is done when user has explicitly pressed "Done" button. `//Presently it is saving even if Cancel button is clicked`
if let indexAt = self.expenses.items.firstIndex( where: { listItem in
return self.item.id == listItem.id
}){
self.expenses.items.remove(at: indexAt)
}
self.item = self.itemUnderEdit
self.expenses.items.append(self.item)
}
}
}
}
struct EditExpenseItemView: View {
@Environment(\.presentationMode) var presentationMode
@Binding var item: ExpenseItem
static private let expenseTypes = ["Personal", "Business"]
var body: some View {
NavigationView{
Form{
TextField("Name", text: self.$item.name)
Picker("Expense Type", selection: self.$item.type) {
ForEach(Self.expenseTypes, id: \.self) {
Text($0)
}
}
TextField("Amount", value: self.$item.amount, formatter: NumberFormatter())
}
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(leading: Button("Cancel"){
self.presentationMode.wrappedValue.dismiss()
}, trailing: Button("Done"){
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
屏幕截图
问题
我希望当用户通过按“完成”按钮完成编辑后,工作表应返回到“只读”屏幕,因为这是用户单击“编辑”按钮的地方。但是由于单击“完成”按钮时我正在修改ListView的数据源,因此ListView会被重新创建/刷新。因此,单击“完成”按钮后,将不再显示EditView工作表,而是返回到ReadOnly视图。
由于我的代码正在更改视图的数据源,因此用户现在无法访问该视图,因此也会生成以下异常
2020-08-02 19:30:11.561793+0530 iExpense[91373:6737004] [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 (e.g. 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. Table view: <_TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView: 0x7f9a8b021800; baseClass = UITableView; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000010a1110>; layer = <CALayer: 0x600001e8c0e0>; contentOffset: {0, -140}; contentSize: {414, 220}; adjustedContentInset: {140, 0, 34, 0}; dataSource: <_TtGC7SwiftUIP13$7fff2c9a5ad419ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_GOS_19SelectionManagerBoxS2___: 0x7f9a8a5073f0>>
我可以理解为什么触发了ListView刷新,但是我无法弄清楚的是编辑模型的正确模式,并且当我们在中间有中间屏幕(即List View-> ReadOnly)时也不会触发ListView刷新->编辑视图。
处理此案有什么建议?