I am new to the WatchOS world and because of that I am in need of some help getting past a point where, even with tutorials, I seem to not be able to do what I am needing to do.
I have a simple watchOS app. Basically its a list with toggles and 2 buttons. What I am wanting to do is, when clicking on said button, have all the toggles change to "off" state. Likewise, the other button will put them all to the "on" state.
Here is my code so far (some code was removed):
import SwiftUI
struct User: Decodable {
var theID: String
var name: String
var description: String
var isOn: Bool
}
var body: some View {
List{
ForEach($users, id: \.theID) { $item in
HStack {
Toggle(isOn: $item.isOn) {
Label {
Text(item.description)
.frame(maxWidth: 85)
.frame(maxHeight: 100)
.frame(maxWidth: .infinity, alignment: .trailing)
} icon: {
if item.name == "J" {
Image("imgJayden")
.scaledToFill()
} else if item.name == "T" {
Image("imgTab")
.scaledToFill()
} else {
Image("imgBoth")
.scaledToFill()
}
}
.onChange(of: item.isOn) { value in
if (rndCnt == 0) {
if (value) {
//print("tunning on")
sendJsonData(a: item.theID, b: item.name, c: item.description, d: item.isOn)
} else {
//print("turning off")
sendJsonData(a: item.theID, b: item.name, c: item.description, d: item.isOn)
}
rndCnt = 1
} else {
rndCnt = 0
}
}
}
}.padding(2)
}
List{
HStack (alignment: .center, spacing: 50){
Button {
}label: {
Image("on")
.scaledToFill()
.frame(maxWidth: .infinity, alignment: .center)
}
.onTapGesture {
print("turned all on!")
}
Button {
}label: {
Image("off")
.scaledToFill()
.frame(maxWidth: .infinity, alignment: .center)
}
.onTapGesture {
print("turned all off!")
}
}
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "http://192.168.1.1:81/data.php?data=ALL") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedResponse = try?
decoder.decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error....")")
}.resume()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
For my list I have the .onAppear(perform: loadData) which is what populates the toggles with the data (thats in function loadData. Now I'm at a standstill on how to loop through all the toggles and set them all to either on or off depending on which button was pressed.
Would be great if someone could help me out on this!
remove the onTapGesture for the Button, they don't need it they are buttons.
Use this example code to turn all users Toggle on/off.
List{
HStack (alignment: .center, spacing: 50){
Button {
print("turned all on!")
users.indices.forEach { users[$0].isOn = true } // <-- here
// alternative
// for i in users.indices { users[i].isOn = true }
}label: {
Image("on")
.scaledToFill()
.frame(maxWidth: .infinity, alignment: .center)
}
Button {
print("turned all off!")
users.indices.forEach { users[$0].isOn = false } // <-- here
}label: {
Image("off")
.scaledToFill()
.frame(maxWidth: .infinity, alignment: .center)
}
}.buttonStyle(.bordered) // <-- here
}
Related
Here's my code, I don't get why this results in this transparent overlay when I move focus to the Text or SecureField components
struct SettingsPane: View {
// MARK: View state
#State private var isSecured: Bool = true
#State private var showApiKeyPopover: Bool = false
// MARK: Preference Storage
#AppStorage("preference_showWorkboard") var showWorkboard = true
#AppStorage("preference_userName") var userName = ""
#AppStorage("preference_jiraApiKey") var jiraApiKey = ""
// MARK: View dimensions
var frameWidth:CGFloat = 200
var body: some View {
Form {
Section(header: Text("Credentials")) {
TextField("User", text: $userName)
.textContentType(.username)
.frame(width: frameWidth)
HStack(alignment: .center) {
Group {
if isSecured {
SecureField("Jira API Key", text: $jiraApiKey)
.textContentType(.password)
.frame(width: frameWidth)
.lineLimit(1)
} else {
TextField("Jira API Key", text: $jiraApiKey)
.textContentType(.password)
.frame(width: frameWidth)
.lineLimit(1)
}
}.popover(isPresented: $showApiKeyPopover) {
Text("To get an API key visit your Jira profile.\nClick 'Personal Access Tokens' and create one named 'Firehose' and add it to this field")
.padding()
}
Button(action: {
isSecured.toggle()
}) {
Image(systemName: self.isSecured ? "eye.slash" : "eye")
.accentColor(.gray)
}
.buttonStyle(.borderless)
Button(action: {
showApiKeyPopover.toggle()
}) {
Image(systemName: "questionmark.circle.fill")
.accentColor(.gray)
}
.buttonStyle(.borderless)
}
Divider()
Section(header: Text("Features")) {
Toggle("Show Workboard", isOn: $showWorkboard)
}
}
}
.padding()
.frame(minWidth: 400, maxWidth: 400)
}
}
It works fine with me (macOS 13.0, Xcode 14.1). I get this ...
Could it be some password utility interfering?
I have an HStack of circles in SwiftUI, and the number of circles is determined based on the length of an array, like this:
#State var myArr = [...]
...
ScrollView(.horizontal) {
HStack {
ForEach(myArr) { item in
Circle()
//.frame(...)
//.animation(...) I tried this, it didn't work
}
}
}
Then I have a button that appends an element to this array, effectively adding a circle to the view:
Button {
myArr.append(...)
} label: {
...
}
The button works as intended, however, the new circle that is added to the view appears very abruptly, and seems choppy. How can I animate this in any way? Perhaps it slides in from the side, or grows from a very small circle to its normal size.
You are missing transition, here is what you looking:
struct ContentView: View {
#State private var array: [Int] = Array(0...2)
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(array, id:\.self) { item in
Circle()
.frame(width: 50, height: 50)
.transition(AnyTransition.scale)
}
}
}
.animation(.default, value: array.count)
Button("add new circle") {
array.append(array.count)
}
Button("remove a circle") {
if array.count > 0 {
array.remove(at: array.count - 1)
}
}
}
}
a version with automatic scroll to the last circle:
struct myItem: Identifiable, Equatable {
let id = UUID()
var size: CGFloat
}
struct ContentView: View {
#State private var myArr: [myItem] = [
myItem(size: 10),
myItem(size: 40),
myItem(size: 30)
]
var body: some View {
ScrollViewReader { scrollProxy in
VStack(alignment: .leading) {
Spacer()
ScrollView(.horizontal) {
HStack {
ForEach(myArr) { item in
Circle()
.id(item.id)
.frame(width: item.size, height: item.size)
.transition(.scale)
}
}
}
.animation(.easeInOut(duration: 1), value: myArr)
Spacer()
Button("Add One") {
let new = myItem(size: CGFloat.random(in: 10...100))
myArr.append(new)
}
.onChange(of: myArr) { _ in
withAnimation {
scrollProxy.scrollTo(myArr.last!.id, anchor: .trailing)
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
.padding()
}
}
}
I'm writing a macOS app in Swiftui, for Big Sur and newer. It's a three pane navigationview app, where the left most pane has the list of options (All Notes in this case), the middle pane is a list of the actual items (title and date), and the last one is a TextEditor where the user adds text.
Each pane is a view that calls the the next view via a NavigationLink. Here's the basic code for that.
struct NoteItem: Codable, Hashable, Identifiable {
let id: Int
var text: String
var date = Date()
var dateText: String {
dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return dateFormatter.string(from: date)
}
var tags: [String] = []
}
struct ContentView: View {
#State var selection: Set<Int> = [0]
var body: some View {
NavigationView {
List(selection: self.$selection) {
NavigationLink(destination: AllNotes()) {
Label("All Notes", systemImage: "doc.plaintext")
}
.tag(0)
}
.listStyle(SidebarListStyle())
.frame(minWidth: 100, idealWidth: 150, maxWidth: 200, maxHeight: .infinity)
Text("Select a note...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
struct AllNotes: View {
#State var items: [NoteItem] = {
guard let data = UserDefaults.standard.data(forKey: "notes") else { return [] }
if let json = try? JSONDecoder().decode([NoteItem].self, from: data) {
return json
}
return []
}()
#State var noteText: String = ""
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: NoteView()) {
VStack(alignment: .leading) {
Text(item.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(item.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
Text("Select a note...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
NewNote()
}) {
Image(systemName: "square.and.pencil")
}
}
}
}
struct NoteView: View {
#State var text: String = ""
var body: some View {
HStack {
VStack(alignment: .leading) {
TextEditor(text: $text).padding().font(.body)
.onChange(of: text, perform: { value in
print("Value of text modified to = \(text)")
})
Spacer()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}
When I create a new note, how can I save the text the user added on the TextEditor in NoteView in the array loaded in AllNotes so I could save the new text? Ideally there is a SaveNote() function that would happen on TextEditor .onChange. But again, given that the array lives in AllNotes, how can I update it from other views?
Thanks for the help. Newbie here!
use EnvironmentObject in App
import SwiftUI
#main
struct NotesApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(DataModel())
}
}
}
now DataModel is a class conforming to ObservableObject
import SwiftUI
final class DataModel: ObservableObject {
#AppStorage("notes") public var notes: [NoteItem] = []
}
any data related stuff should be done in DataModel not in View, plus you can access it and update it from anywhere, declare it like this in your ContentView or any child View
NoteView
import SwiftUI
struct NoteView: View {
#EnvironmentObject private var data: DataModel
var note: NoteItem
#State var text: String = ""
var body: some View {
HStack {
VStack(alignment: .leading) {
TextEditor(text: $text).padding().font(.body)
.onChange(of: text, perform: { value in
guard let index = data.notes.firstIndex(of: note) else { return }
data.notes[index].text = value
})
Spacer()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
.onAppear() {
print(data.notes.count)
}
}
}
AppStorage is the better way to use UserDefaults but AppStorage does not work with custom Objects yet (I think it does for iOS 15), so you need to add this extension to make it work.
import SwiftUI
struct NoteItem: Codable, Hashable, Identifiable {
let id: UUID
var text: String
var date = Date()
var dateText: String {
let df = DateFormatter()
df.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return df.string(from: date)
}
var tags: [String] = []
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
Now I changed AllNotes view to work with new changes
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var noteText: String = ""
var body: some View {
NavigationView {
List(data.notes) { note in
NavigationLink(destination: NoteView(note: note)) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
Text("Select a note...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
data.notes.append(NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []))
}) {
Image(systemName: "square.and.pencil")
}
}
}
}
}
I created a simple collection with a button jump to the next View. From the last View there should be a transition to AddItemView, but it doesn't happen - it goes back to the first screen.
Can you tell me where I made a mistake?
What is the correct way to place the background Image on the first collection screen, so that it won't be on the following screens?
import SwiftUI
struct AddItemView: View {
var body: some View {
Text("Hallo!")
}
}
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
ForEach(0..<emojis.endIndex) { index in
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.endIndex {
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In this code, you have not to use NavigationView. It's required to navigate to the next screen. Similar concept like Push view controller if navigation controller exists. Also, remove endIndex and use indices.
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
NavigationView { //<- add navigation view
TabView(selection: $tabSelection) {
ForEach(emojis.indices) { index in //<-- use indices
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.count - 1 { //<- use count
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
}
If you have already a navigation link from the previous screen then, the problem is you are using endIndex in the wrong way. Check this thread for correct use (https://stackoverflow.com/a/36683863/14733292).
I have implemented a sheet to edit the values of a client.
It's normally possible to edit the client and close the sheet after pressing the OK-Button. But if the sheet is open for a longer time it is not possible to dismiss the sheet. Nothing happens and they only way to proceed is to quit the program.
Does anyone have an idea why this happens sometimes?
struct ContentView: View {
#State private var showingEditClient = false
var body: some View {
VStack{
HStack {
Button(action: showEditClientSheet) {
Text("Edit Client")
}
.sheet(isPresented: $showingEditClient) {
EditClientSheet()
}
}
}
.frame(minWidth: 400, minHeight: 400)
}
func showEditClientSheet(){
showingEditClient.toggle()
}
}
struct EditClientSheet: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = "Max"
var body: some View {
VStack {
Form {
TextField("Name", text: $name)
}
HStack{
Button(action: cancel) {
Text("Abbrechen")
}
Button(action: editClient) {
Text("Ok")
}
}
}
.frame(minWidth: 200, minHeight: 200)
}
func editClient() {
NSApp.keyWindow?.makeFirstResponder(nil)
//Check if content is correct to save
if name != "" {
//store the changes
self.presentationMode.wrappedValue.dismiss()
}else {
//show Alert
}
}
func cancel() {
self.presentationMode.wrappedValue.dismiss()
}
}