i try to insert a simple alert to a button, when my Textfields are empty, but I receive the Error message Alert.Button is not convertible to 'Alert.Button?'...
I would say that my code is 100% correct, but.. Xcode is not on my side..
This is my code,..
struct ContentView : View {
#State private var name = String()
#State private var pw = String()
#State private var myAlert = false
var body: some View {
NavigationView {
ZStack{
VStack{
//deleted code//
Button(action: {
if self.name == self.fileName
{
print("Hey \(self.name) - your password is \(self.pw)")
} else {
if self.name.isEmpty && self.pw.isEmpty {
self.myAlert = true
print("broken!")
}
}
.alert(isPresented: $myAlert) {
Alert(title: Text("ERROR!"), message: Text("Username und Password are empty"), dismissButton:.default(Text("Ok")))
}
})
//deleted code//
}}}}}
Does anybody know where the mistake is? I can't found them.
Thanks for your help!
1) There is no fileName?
2) The button is missing a Label:
Button(action: {...}) {
//Label here
}
Related
I can't find what is wrong, I'm try to monitor the internet connectivity using swiftui.
I follow some tutorial online..
but it doest work for me.
can someone help me to find what is wrong on this code?
I'm using the simulator for testing, when I switch off the wifi of the iMac the variable isConnected still print out "we have internet!" when I switch on the wifi it print "no internet"
what is wrong in this code?
class DataManager: ObservableObject {
let monitor = NWPathMonitor()
let queue = DispatchQueue.global(qos: .background)
#Published var isConnected = true
init() {
monitor.pathUpdateHandler = { path in
DispatchQueue.main.async {
// codice eseguito sul thread principale
if path.status == .satisfied {
print("Yay! We have internet!")
self.isConnected = true
} else {
print("NOOO! no internet!")
self.isConnected = false
}
}
}
monitor.start(queue: queue)
}
}
struct ContentView: View {
#ObservedObject var dm = DataManager()
var body: some View {
VStack {
if dm.isConnected == true {
Text("connesso")
} else {
Text("non connesso")
}
}
}
}
I am working on incorporating a Firestore repository into a SwiftUI 2 app. The list view loads appropriately, and refreshes automatically when the data in the Firestore collection is changed. A NavigationLink loads the appropriate DetailView correctly. A button on the DetailView is set to change data within the relevant document, and works correctly, but the DetailView page does not refresh with the correct data (it appears correctly when exiting to the list view and returning); likewise the DetailView will not respond to changes to the collection made elsewhere.
I have attempted to use various configurations of ObservedObject and State, but have been unable to get the result intended.
Any help would be appreciated. Snipped Code listed below shows the flow, and uses the status field as an example.
CustomerRepository
class CustomerRepository: ObservableObject {
private let path: String = "customers"
private let store = Firestore.firestore()
#Published var customers: [Customer] = []
private var cancellables: Set<AnyCancellable> = []
init() {
store.collection(path)
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("Error getting customers \(error.localizedDescription)")
return
}
self.customers = querySnapshot?.documents.compactMap { document in
try? document.data(as: Customer.self)
} ?? []
}
}
func update(_ customer: Customer) {
guard let customerID = customer.id else { return }
do {
try store.collection(path).document(customerID).setData(from: customer)
} catch {
fatalError("Unable to update record: \(error.localizedDescription)")
}
}
}
CustomerListViewModel
class CustomerListViewModel: ObservableObject {
#Published var customerViewModels: [CustomerViewModel] = []
private var cancellables: Set<AnyCancellable> = []
#Published var customerRepository = CustomerRepository()
init() {
customerRepository.$customers.map { customers in
customers.map(CustomerViewModel.init)
}
.assign(to: \.customerViewModels, on: self)
.store(in: &cancellables)
}
}
CustomerViewModel
class CustomerViewModel: ObservableObject, Identifiable {
private let customerRepository = CustomerRepository()
#Published var customer: Customer
private var cancellables: Set<AnyCancellable> = []
var id = ""
init(customer: Customer) {
self.customer = customer
$customer
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func update(customer: Customer) {
customerRepository.update(customer)
}
}
CustomerListView
struct CustomerListView: View {
#ObservedObject var customerListViewModel = CustomerListViewModel()
var body: some View {
NavigationView {
List {
ForEach(customerListViewModel.customerViewModels) { customerViewModel in
NavigationLink(
destination: CustomerDetailView(customerViewModel: customerViewModel)) {
CustomerCell(customerViewModel: customerViewModel)
}
}
}
}
}
}
CustomerDetailView
struct CustomerDetailView: View {
var customerViewModel: CustomerViewModel
var body: some View {
VStack {
Text(customerViewModel.customer.status.description)
}.onTapGesture {
nextTask()
}
}
private func nextTask() {
switch customerViewModel.customer.status {
case .dispatched:
customerViewModel.customer.status = .accepted
...
default:
return
}
update(customer: customerViewModel.customer)
}
func update(customer: Customer) {
customerViewModel.update(customer: customer)
}
}
After some reconfiguring, and the help of this example:
https://peterfriese.dev/swiftui-firebase-update-data/
I was able to solve my issue. Below is a breakdown of the revised code in case it will help others....
CustomerViewModel
class CustomerViewModel: ObservableObject, Identifiable {
#Published var customer: Customer
#Published var modified = false
private var cancellables: Set<AnyCancellable> = []
init(customer: Customer = Customer(status: .new)) {
self.customer = customer
self.$customer
.dropFirst()
.sing { [weak self] customer in
self?.modified = true
}
.store(in: &cancellables)
}
func handleDoneTapped() {
self.updateOrAddCustomer()
}
}
CustomerListView
// Snip to Navigation Link
NavigationLink(
destination: CustomerDetailView(customerViewModel: CustomerViewModel(customer: customerViewModel.customer))) {
CustomerCell(customerViewModel: customerViewModel)
}
CustomerDetailView
struct CustomerDetailView: View {
#ObservedObject var customerViewModel = CustomerViewModel()
var body: some View {
VStack {
Text(customerViewModel.customer.status.description)
}.onTapGesture {
nextTask()
}
}
private func nextTask() {
switch customerViewModel.customer.status {
case .dispatched:
customerViewModel.customer.status = .accepted
...
default:
return
}
update(customer: customerViewModel.customer)
}
func update(customer: Customer) {
customerViewModel.handleDoneTapped()
}
}
Here you need to put the parameters for which values being updated and want the UI to update accordingly:
class CustomerViewModel: ObservableObject, Identifiable {
private let customerRepository = CustomerRepository()
#Published var customer: Customer
private var cancellables: Set<AnyCancellable> = []
var id = ""
var status: StatusEnum
init(customer: Customer) {
self.customer = customer
self.status = customer.status
$customer
.compactMap { $0.status }
.assign(to: \.status, on: self)
.store(in: &cancellables)
$customer
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
func update(customer: Customer) {
customerRepository.update(customer)
}
}
Then, use this new status variable in the view: Text(customerViewModel.status.description).
How can i make custom Prompt?
I tried with code below..
public static string ShowDialog(string text, string caption) {
Form prompt = new Form() {
Width = 500,
Height = 150,
FormBorderStyle = FormBorderStyle.FixedDialog,
Text = caption,
StartPosition = FormStartPosition.CenterScreen
};
Label textLabel = new Label() { Left = 50, Top = 20, Text = text };
TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 400 };
Button confirmation = new Button() { Text = "Ok", Left = 350, Width = 100, Top = 70, DialogResult = DialogResult.OK };
confirmation.Click += (sender, e) => { prompt.Close(); };
prompt.Controls.Add(textBox);
prompt.Controls.Add(confirmation);
prompt.Controls.Add(textLabel);
prompt.AcceptButton = confirmation;
return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : "";
}
And then am using it like below
public bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
if(dialogType.ToString() == "Prompt") {
//Form prompt = ShowDialogClass.ShowDialog("as", "asd");
string promptValue = Components.ShowDialog("Test", "123");
if (promptValue != "") {
callback.Continue(true, promptValue);
} else {
callback.Continue(false, "");
};
};
But i am getting error.
System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
return false;
}
How can i implement this dialog to show custom prompt?
Few months too late but, here you go.
You are trying to create a new Form(your prompt form) inside another thread. In this case your CEF browser thread that will create a object from class IJsDialogHandler will be on another thread than the prompt message thread so you have to Cross the thread to access it.
The way you do this is "Invoke"(saying something like "wo wo don't worry, i know what i'm doing"). When you use "Invoke" your asking for a witness, well that witness should have the same kind of capabilities as your prompt message box form so.... in this case form that creates the CEF browser. so the code should be something like this
public bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
if(dialogType.ToString() == "Prompt") {
if (ParentForm.InvokeRequired)
{
ParentForm.Invoke((MethodInvoker)delegate ()
{
string promptValue = Components.ShowDialog(messageText, "Prompt Message");
if (promptValue != "") {
callback.Continue(true, promptValue);
} else {
callback.Continue(false);
}
}
}
suppressMessage = false;
return true;
}
}
ParentForm should be changed to the name of the form that initialize the CEF browser.
I'm creating a custom component which is a list of Buttons.
When the user clicks on a button, i change its css class and then i would like to add it in a custom "selectedItems" property to retrieve it in my ViewModel.
When I do a push on the "selectedItems" array property, no event is raised and I don't get the information.
Also, I tried to re-set the entire array but not better.
I don't know how to achieve this.
Here is the code of my component :
import {WrapLayout} from "ui/layouts/wrap-layout";
import {EventData} from "data/observable";
import {ValueButton} from "./value-button";
import dependencyObservableModule = require("ui/core/dependency-observable");
export class ValuesSelector extends WrapLayout {
public static itemsProperty = new dependencyObservableModule.Property(
"items",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
[],
dependencyObservableModule.PropertyMetadataSettings.None,
function(data: dependencyObservableModule.PropertyChangeData) {
if (data.newValue) {
let instance = <ValuesSelector>data.object;
instance.items = data.newValue;
}
}));
public static deleteOnClickProperty = new dependencyObservableModule.Property(
"deleteOnClick",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
false,
dependencyObservableModule.PropertyMetadataSettings.None));
public static selectedItemsProperty = new dependencyObservableModule.Property(
"selectedItems",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
[],
dependencyObservableModule.PropertyMetadataSettings.None));
public static singleSelectionProperty = new dependencyObservableModule.Property(
"singleSelection",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
false,
dependencyObservableModule.PropertyMetadataSettings.None));
public get selectedItems() {
return this._getValue(ValuesSelector.selectedItemsProperty);
}
public set selectedItems(value: any[]) {
this._setValue(ValuesSelector.selectedItemsProperty, value);
}
public get deleteOnClick() {
return this._getValue(ValuesSelector.deleteOnClickProperty);
}
public set deleteOnClick(value: boolean) {
this._setValue(ValuesSelector.deleteOnClickProperty, value);
}
public get singleSelection() {
return this._getValue(ValuesSelector.singleSelectionProperty);
}
public set singleSelection(value: boolean) {
this._setValue(ValuesSelector.singleSelectionProperty, value);
}
public get items() {
return this._getValue(ValuesSelector.itemsProperty);
}
public set items(value: any) {
this._setValue(ValuesSelector.itemsProperty, value);
this.createUI();
}
private _buttons: ValueButton[];
constructor() {
super();
this.orientation = "horizontal";
this._buttons = [];
}
private createUI() {
this.removeChildren();
let itemsLength = this.items.length;
for (let i = 0; i < itemsLength; i++) {
let itemButton = new ValueButton();
itemButton.text = this.items[i].label;
itemButton.value = this.items[i];
itemButton.className = "values-selector-item";
if (this.deleteOnClick) {
itemButton.className = "values-selector-selected-item";
}
itemButton.on(ValueButton.tapEvent, (data: EventData) => {
let clickedButton = <ValueButton>data.object;
if (this.deleteOnClick) {
let itemIndex = this.items.indexOf(clickedButton.value);
if (itemIndex > -1) {
let newSelectedItems = this.items;
newSelectedItems.splice(itemIndex, 1);
this.items = newSelectedItems;
}
return;
}
let internalSelectedItems = this.selectedItems;
if (clickedButton.className === "values-selector-item") {
if (this.singleSelection && this.selectedItems.length > 0) {
internalSelectedItems = [];
for (let i = 0; i < this._buttons.length; i++) {
this._buttons[i].className = "values-selector-item";
}
}
internalSelectedItems.push(clickedButton.value);
clickedButton.className = "values-selector-selected-item";
} else {
let itemIndex = internalSelectedItems.indexOf(clickedButton.value);
if (itemIndex > -1) {
internalSelectedItems.splice(itemIndex, 1);
}
clickedButton.className = "values-selector-item";
}
this.selectedItems = internalSelectedItems;
}, this);
this._buttons.push(itemButton);
this.addChild(itemButton);
}
}
}
Can you help me ?
Thanks
Ok I made a mistake by databinding my property.
In fact, in the XML I use the component like this :
<vs:ValuesSelector items="{{ criterias }}" selectedItems="{{ myObject.selectedCriterias }}" />
But in the ViewModel, I never initialized the selectedCriterias property because I thought that the default value [] specified in the component would create it.
So in the ViewModel, here is the fix :
Before
this.myObject = {
id : 0
};
After
this.myObject = {
id : 0,
selectedCriterias: []
};
I am trying to bind two views of viewmodel to two tabs of tab control by editing sample source code Caliburn.Micro.SimpleMDI included with Caliburn.Micro source. This project contains ShellViewModel and TabViewModel with TabView. I added one View named TabViewDetails. I edited ShellViewModel as follows.
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
int count = 1;
public void OpenTab()
{
TabViewModel vm = null;
if (Items.Count != 0)
{
vm = new TabViewModel() { DisplayName = "Detail Tab " + count++ };
var secondView = new TabViewDetails();
ViewModelBinder.Bind(vm, secondView , null);
}
else
{
vm = new TabViewModel() { DisplayName = "Tab " + count++ };
}
ActivateItem(vm);
}
}
First tab is Ok. But the second tab shows nothing.Can anybody help to figure out the problem?.
I haven't used Caliburn.Micro much but the simple solution is one view, one view model. If you change your code to something like:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
int count = 1;
public void OpenTab()
{
Screen screen;
if (count != 0)
{
screen = new TabViewModel
{
DisplayName = "Tab " + _count++
};
}
else
{
screen = new TestViewModel
{
DisplayName = "Tab " + _count++
};
}
ActivateItem(screen);
}
}
where TestViewModel can be a TabViewModel
public class TestViewModel : TabViewModel
{
}
then this works ok.
The Caliburn docs does have a section multiple views over the same viewmodel but I haven't figured that out yet.