In a simple test project at Github I am trying to display a Menu with 3 languages and the flags to allow users select the localization:
struct ContentView: View {
// ...some Core Data related code skipped...
let labels = [
"en" : "πΊπΈ EN",
"de" : "π©πͺ DE",
"ru" : "π·πΊ RU"
]
#AppStorage("language") var language:String = "en"
var body: some View {
VStack(alignment: .trailing) {
Menu(language) {
Button("πΊπΈ EN", action: { language = "en" })
Button("π©πͺ DE", action: { language = "de" })
Button("π·πΊ RU", action: { language = "ru" })
}.padding()
List {
ForEach(topEntities) { top in
TopRow(topEntity: top)
}
}
}.environment(\.locale, .init(identifier: language))
}
}
The above code seems to work ok, but has one cosmetic problem: the Menu displays the selected language a simple string "en" (or "de", or "ru"):
Being a Swift and SwiftUI newbie I do not understand, how to set the label to the nicer string, i.e. to the selected language and flag, like "πΊπΈ EN". Please help
You can get the nice string from your labels dictionary. Here is the working version:
Menu(labels[language] ?? "Unknown") {
Button("πΊπΈ EN", action: { language = "en" })
Button("π©πͺ DE", action: { language = "de" })
Button("π·πΊ RU", action: { language = "ru" })
}.padding()
I just replaced language with labels[language] ?? "Unknown".
Related
If I have a SwiftUI view declaration like:
ProfileView(userData, onDoneEdit: {
withAnimation {
isEditingViewPresented.toggle()
}
...
}, onCancel: { ... })
does anyone know if it can be implemented a custom annotation that let me have:
ProfileView(userData, onDoneEdit: { #withAnimation
isEditingViewPresented.toggle()
...
}, onCancel: { ... })
This question is more for learning purpose as I'm experimenting with SwiftUI.
I am new to SwiftUI and programming in general. I am trying to pass data and create navigation between different views in my app.
For my data model, I am using MVVM format even though my data is entirely static right now. I have two data models that I am trying to connect via enumeration: CategoryModel and SubCategoryModel (see below).
CategoryModel:
import Foundation
import SwiftUI
enum Category: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case explicit = "Explicit"
case adventure = "Adventure"
case artists = "Artists"
case holidays = "Holidays"
case movies = "Movies"
case people = "People"
case tasks = "Tasks"
case feelings = "Feelings"
case lifestyle = "Lifesytle"
case party = "Party"
case sports = "Sports"
}
//Root data -> set up data structure
//make Identifiable for ForEach looping in other views
struct CategoryModel: Identifiable {
let id = UUID().uuidString
let category: Category
let categoryTitle: String
let description: String
let isPurchase: Bool
let categoryImage: Image
}
class CategoryViewModel: ObservableObject {
#Published var gameCategories: [CategoryModel] = CategoryModel.all
#Published var filteredPurchase: [CategoryModel] = []
init() {
filterByPurchase()
}
func filterByPurchase() {
filteredPurchase = gameCategories.filter({ $0.isPurchase })
}
}
extension CategoryModel {
static let all = [
CategoryModel(
category: .explicit,
categoryTitle: "Adults Only",
description: "For those spicy, intimate moments.",
isPurchase: true,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
CategoryModel(
category: .adventure,
categoryTitle: "Call of the Wild",
description: "[Insert description here]",
isPurchase: false,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Adventure"))
),
CategoryModel(
category: .artists,
categoryTitle: "By the Artist",
description: "[Insert description here]",
isPurchase: false,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Artists"))
),
]
}
SubCategoryModel:
import Foundation
import SwiftUI
//Root data -> set up data structure
//make Identifiable for ForEach looping in other views
struct SubCategoryModel: Identifiable {
let id = UUID().uuidString
let category: Category
let subcategory: String
let subtitle: String
let subdescription: String
let instruction: String
let cardImage: Image
}
class SubCategoryViewModel: ObservableObject {
#Published var gameSubCategories: [SubCategoryModel] = SubCategoryModel.allSubs
#Published var filteredCategory: [SubCategoryModel] = []
#Published var categoryType: Category = .explicit
init() {
filterByCategory()
}
func filterByCategory() {
DispatchQueue.global(qos: .userInteractive).async {
let results = self.gameSubCategories
.lazy
.filter { item in
return item.category == self.categoryType
}
DispatchQueue.main.async {
self.filteredCategory = results.compactMap({ item in
return item
})
}
}
}
}
extension SubCategoryModel {
static let allSubs = [
SubCategoryModel(
category: .explicit,
subcategory: "Significant Other",
subtitle: "Bedroom Eyes",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
SubCategoryModel(
category: .explicit,
subcategory: "Dating",
subtitle: "First Date",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
SubCategoryModel(
category: .adventure,
subcategory: "Hiking",
subtitle: "Bedroom Eyes",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Adventure"))
),
]
}
My goal is to click on a card from the CategoryView screen and navigate to the SubCategoryView via a navigation link. I want the SubCategoryView to show a filtered list of subcategories based on the category selected on the CategoryView screen.
CategoryView to SubCategoryView GIF
CategoryLoop code snippet:
struct CategoryLoop: View {
let categories: [CategoryModel]
var body: some View {
ZStack {
ScrollView {
VStack {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 20)], spacing: 20) {
ForEach(categories) { item in
NavigationLink(destination: SubCategoryView()
.navigationTitle(item.category.rawValue)) {
CategoryCard(category: item)
}
}
}
Based on the code in my CategoryLoop file, what is the best/easiest way to pass my model data and filter the list on the SubCategoryView? I am having trouble figuring out how to use the enumeration. Is it possible to write a function that would update #Published var categoryType: Category = .explicit (see SubCategoryModel) based on the card clicked in the LazyVGrid? Also, if you have suggestion on how to better organise my data models, please let me know.
I have the following view:
struct Menu: View {
let sctions:[TestSection] = [
TestSection(id: 0, name: "One", items: [
ListItem(id: 0, name: "1"),
ListItem(id: 1, name: "2"),
ListItem(id: 2, name: "3"),
ListItem(id: 3, name: "4")
]),
TestSection(id: 1, name: "Two", items: [
ListItem(id: 4, name: "4"),
ListItem(id: 5, name: "5"),
ListItem(id: 6, name: "6"),
ListItem(id: 7, name: "7")
]),
TestSection(id: 2, name: "Three", items: [
ListItem(id: 8, name: "8"),
ListItem(id: 9, name: "9"),
])
]
var body: some View {
NavigationView {
List {
ForEach(sctions) { section in
Section(header: Text(section.name)) {
ForEach(section.items) { item in
TestCell(item: item)
}
}
}
}
.listStyle(.plain)
.navigationBarTitle("Title")
}
}
}
struct TestCell: View {
#ObservedObject private var model:ItemModel
let item:ListItem
init(item:ListItem) {
self.item = item
self.model = ItemModel(itemId:item.id)
}
var body: some View {
Text("item: \(item.name)")
}
}
class ItemModel:ObservableObject {
#Published var someProperty:Int
let itemId:Int
init(itemId:Int) {
self.itemId = itemId
self.someProperty = 0
}
}
I am trying to decide how to handle child views in SwiftUI from a model layer point of view.
One option is #ObservedObject in the child view. The parent created the model and sets it on the child or the parent passes in a property on the child that then allows the child to init the model in its initializer:
init(item:ListItem) {
self.item = item
self.model = ItemModel(itemId:item.id). // <<---- HERE
}
Then looking at this, I wonder if this is less performant than using a #StateObject in the child view that would manage its model's lifecycle.
So then I tried this:
struct TestCell: View {
#StateObject var model = ItemModel() // <<-- error "Missing argument for parameter 'itemId' in call"
let item:ListItem
var body: some View {
Text("item: \(item.name)")
}
}
Which, obviously shows the error:
I am not sure how to initialize ItemModel in TestCell when using the #StateObject approach.
The question I have is:
in this type of scenario, where I want each cell to have its own model (to handle model related logic like making network calls, updating model layer etc...) should I be creating the model as I instantiate the cell within the parent during cell creation, and setting it on the cell (#ObservedObject)?
Or should a cell use #StateObject for its model, and if so, how would I have the model initialize itself properly when it needs parameters, like let itemId:Int for example?
Your first option is not recommended because
init(item:ListItem) {
self.item = item
self.model = ItemModel(itemId:item.id). // <<---- HERE
}
itβs unsafe to create an observed object inside a view
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
You will find many questions in SO that talk about Views being unreliable. You'll also find the ones that tell people to do it this way but you will see issues with it.
If you use #StateObject var model: ItemModel = ItemModel() which is the only exception according to Apple for creating an ObservableObject In a View
And switch
let itemId:Int
in your ItemModel to
var itemId:Int = 0
Then in TestCell add to the outermost View
.onAppear(){
model.itemId = item.id
}
Or depending on your ParentView and its ViewModel you can create the ObservableObjects in the class ViewModel and pass it as a parameter for
#ObservedObject private var model:ItemModel
The important thing is that the workaround takes into consideration that an ObservableObject should not be created in a View with the exception of #StateObject. SwiftUI reserves the right to re-create any View whenever it decides that it is necessary.
I want to filter my data array based on the picker selection. My question is how to populate/filter cities array based on any selected country. The sample code is as follows:
static var countries: [String: [WorldData]] {
Dictionary(
grouping: worldData,
by: { $0.country }
)
}
static var city: [String: [WorldData]] {
Dictionary(
grouping: worldData,
by: { $0.city }
)
}
Picker(selection: $Country,
label: Text("Country")) {
ForEach(self.countries.keys.sorted(), id: \.self) { key in
Text(key).tag(key)
}
}
After a good sleep, I solved my own problem. If anyone needs it, here is the solution:
static func filteredCities(key:String) -> [String: [WorldData]] {
var cities: [String: [WorldData]] {
Dictionary(
grouping: worldData.filter{$0.species == key},
by: { $0.city }
)
}
return cities
}
I am working with Odoo10. If I go to Sales > Lead > Meeting Button and I click on the meeting button the calendar view is opened. You can open the view by creating a meeting in the calendar as well. The model used by the popup window is calendar.event.
These buttons appear in the wizard: "Save", "Delete", "Cancel". The wizard does not contain the code of "Delete" button in the standard view.
So how can I remove the "Delete" button in that popup?
I have checked that the button is created by JavaScript. You just need to override the method. Follow the Odoo documentation guidelines. Use extend or include to override it
var CalendarView = View.extend({
// [...]
open_event: function(id, title) {
var self = this;
if (! this.open_popup_action) {
var index = this.dataset.get_id_index(id);
this.dataset.index = index;
if (this.write_right) {
this.do_switch_view('form', { mode: "edit" });
} else {
this.do_switch_view('form', { mode: "view" });
}
}
else {
new form_common.FormViewDialog(this, {
res_model: this.model,
res_id: parseInt(id).toString() === id ? parseInt(id) : id,
context: this.dataset.get_context(),
title: title,
view_id: +this.open_popup_action,
readonly: true,
buttons: [
{text: _t("Edit"), classes: 'btn-primary', close: true, click: function() {
self.dataset.index = self.dataset.get_id_index(id);
self.do_switch_view('form', { mode: "edit" });
}},
{text: _t("Delete"), close: true, click: function() {
self.remove_event(id);
}},
{text: _t("Close"), close: true}
]
}).open();
}
return false;
},
So I think if you remove these lines would be enough:
{text: _t("Delete"), close: true, click: function() {
self.remove_event(id);
}},
By the way, as you can see in the last link, the file to modify (by inheritance) is addons/web_calendar/static/src/js/web_calendar.js