SwiftUI SystemColors for Lists - colors

I have a list in witch I want to highlight a selected line.
This works fine with:
struct ContentView: View {
var body: some View {
List {
Line(text: "Line 1")
Line(text: "Line 2")
Line(text: "Line 3",selected: true)
Line(text: "Line 4")
}
}
}
struct Line: View {
var text :String
var selected = false
var body: some View {
Text(text)
.listRowBackground(selected ? Color.blue : Color.white)
.foregroundColor(selected ? Color.white : Color.black)
}
}
However when switching to darkmode it looks ugly.
Ofcourse I could detect dark mode and set the color explicitly, but I am looking for a way to set the color for the "not selected" line to the standard foreground and backgroundcolor of a list.
How can I get these "Systemcolors"

A simple way to access system colors is with Color(UIColor) like this:
var body: some View {
Text(text)
.listRowBackground(Color(.systemBackground))
.foregroundColor(Color(.label))
}

Related

How to select an item from a search file and place in textfield in another file

Using SwiftUI - Xcode 14.2 - iOS 16.0
I have tried different search tutorials to create a search file for my project but am unable to find out how to select the item in the search file and place that selected item in a textfield in another file. I have searched this site for other posts, i tried searching through Google, YouTube, etc...
In File 1, I have a textfield that that has a prompt 'start typing' and when selected, it directs you to the Search file to select the item you want, so it can be placed in place of the prompt.
File 1 (where the textfield is needed to paste the selected item):
VStack {
NavigationLink(destination: NameSearch()) {
TextField("Name", text: .constant(""), prompt: Text(" Start typing ")
.foregroundColor(.blue))
.multilineTextAlignment(.leading)
.padding()
}
}
Once I click on the 'start typing' prompt, it navigates to NameSearch.swift file, as seen below.
NameSearch.swift:
import SwiftUI
struct NameSearch: View {
let name = [
"Jane", "George", "Sam", "Henry", "Sally", "Liz", "John"
]
#State private var searchText = ""
var body: some View {
NavigationStack {
VStack {
// Search view
SearchBarView(searchText: $searchText)
List {
// Filtered list of names
ForEach(name.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self) {
searchText in Text(searchText)
}
}
.navigationBarTitle(Text("Search Name"))
.resignKeyboardOnDragGesture()
}
}
}
}
struct NameSearch_Previews: PreviewProvider {
static var previews: some View {
Group {
NameSearch()
.environment(\.colorScheme, .light)
NameSearch()
.environment(\.colorScheme, .dark)
}
}
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
modifier(ResignKeyboardOnDragGesture())
}
}
struct SearchBarView: View {
#Binding var searchText: String
#State private var showCancelButton: Bool = false
var onCommit: () ->Void = {print("onCommit")}
var body: some View {
HStack {
HStack {
Image(systemName: "magnifyingglass")
// Search text field
ZStack (alignment: .leading) {
if searchText.isEmpty { // Separate text for placeholder to give it the proper color
Text("Search")
}
TextField("", text: $searchText, onEditingChanged: { isEditing in
self.showCancelButton = true
}, onCommit: onCommit).foregroundColor(.primary)
}
// Clear button
Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary) // For magnifying glass and placeholder test
.background(Color(.tertiarySystemFill))
.cornerRadius(10.0)
if showCancelButton {
// Cancel button
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton)
}
}
Question 1: How do I hide all the names from showing in the list so that I just see the search bar and the cancel button and an empty list?
Question 2: Once I type the name I am looking for, it should pop up and I want to select name - how can I do this?
once I type the name in search bar, it appears in the empty list
I select that name
it then takes me back to File 1
replaces the 'start typing' prompt with the name i just selected in the Search file.
Question 3: I have noticed in the Search file, I am getting a warning with the following code. How can I resolve it?
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
The warning that appears is:
'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a
relevant window scene instead
Firstly, thank you for providing a working example of your code.
As you're building for iOS 15+, you should probably be using the .searchable modifier rather than rolling your own.
The 2021 WWDC video introducing this feature is here https://developer.apple.com/wwdc21/10176
Some new features from 2022 here: https://developer.apple.com/wwdc22/10052

SwiftUI UISearchController replacement: search field, results and some scrollable content fail to coexist in a meaningful manner

Starting with this
var body: some View {
ScrollView {
VStack(spacing: 0.0) {
Some views here
}
}
.edgesIgnoringSafeArea(.top)
}
How would I add
List(suggestions, rowContent: { text in
NavigationLink(destination: ResultsPullerView(searchText: text)) {
Text(text)
}
})
.searchable(text: $searchText)
on top if that scrollable content?
Cause no matter how I hoax this together when
#State private var suggestions: [String] = []
gets populated (non empty) the search results are not squeezed in (or, better yet, shown on top of
"Some views here"
So what I want to achieve in different terms: search field is on top, scrollable content driven by the search results is underneath, drop down with search suggestions either temporarily squeeses scrollable content down or is overlaid on top like a modal sheet.
Thanks!
If you are looking for UIKit like search behaviour you have to display your results in an overlay:
1. Let's declare a screen to display the results:
struct SearchResultsScreen: View {
#Environment(\.isSearching) private var isSearching
var results: [String]?
var body: some View {
if isSearching, let results {
if results.isEmpty {
Text("nothing to see here")
} else {
List(results, id: \.self) { fruit in
NavigationLink(destination: Text(fruit)) {
Text(fruit)
}
}
}
}
}
}
2. Let's have an ObservableObject to handle the logic:
class Search: ObservableObject {
static private let fruit = [
"Apples 🍏",
"Cherries 🍒",
"Pears 🍐",
"Oranges 🍊",
"Pineapples 🍍",
"Bananas 🍌"
]
#Published var text: String = ""
var results: [String]? {
if text.isEmpty {
return nil
} else {
return Self.fruit.filter({ $0.contains(text)})
}
}
}
3. And lastly lets declare the main screen where the search bar is displayed:
struct ContentView: View {
#StateObject var search = Search()
var body: some View {
NavigationView {
LinearGradient(colors: [.orange, .red], startPoint: .topLeading, endPoint: .bottomTrailing)
.overlay(SearchResultsScreen(results: search.results))
.searchable(text: $search.text)
.navigationTitle("Find that fruit")
}
}
}

Speed Up TextEditing on Long Text

With the following code performance degrades drastically as the text gets longer due to the view getting redrawn for every character typed (due to textViewDidChange). There are also a number of nasty side effects as well, such as insertion of sentence breaks likely due to autocorrect or some other mechanism. If I comment out or eliminate the textViewDidChange function, performance is much better and the side effects disappear, but then I no longer can capture the text to save it or interrogate it on the fly.
Does anyone know of a way around this? I thought that if I could just not refresh the text variable until the focus was moved to a different view, then I could capture the text before it needs to be saved. If that might work, I can't determine how to trap the focus change. I have tried implementing textViewDidEndEditing but that only gets triggered when the parent view ends editing. I'm using a save button and want to capture the text then.
I realize that there is a TextEditor view available, but it is just as slow on large text so I assume it is doing the same thing.
import SwiftUI
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .none
textView.isSelectable = true
textView.isUserInteractionEnabled = true
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
Content View
import SwiftUI
struct ContentView: View {
#State private var message = ""
#State private var textStyle = UIFont.TextStyle.body
var body: some View {
ZStack(alignment: .topTrailing) {
TextView(text: $message, textStyle: $textStyle)
.padding(.horizontal)
Button(action: {
self.textStyle = (self.textStyle == .body) ? .title1 : .body
}) {
Image(systemName: "textformat")
.imageScale(.large)
.frame(width: 40, height: 40)
.foregroundColor(.white)
.background(Color.purple)
.clipShape(Circle())
}
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Core Data observed object in SwiftUI

I have created a list of clients which get saved in core data. I added some attributes through an AddView and now I am trying to add a new attribute using a different view. In order to do that I understood I need to use the observedObject property wrapper so that it doesn't create a new client but update the existing one. This is the code:
import SwiftUI
import CoreData
struct TopicView: View {
#Environment(\.managedObjectContext) var managedObjectContext
let myStudent: StudentData
#ObservedObject var topic: StudentData
#State var content = ""
#State private var show = false
var body: some View {
Section (header: Text("Topics covered")
.bold()
.padding(.all)
.font(.title3)) {
TextField("Add content", text: $topic.content ?? "") //error here
//Text(topic.content ?? "")
.padding()}
Button ("Save")
{ let newStudent = StudentData(context: self.managedObjectContext)
newStudent.content = self.topic.content
try? self.managedObjectContext.save()
self.topic.content = ""
}.foregroundColor(.blue)
.cornerRadius(10)
.frame(width: 200, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.font(.headline)
Spacer()
}
}
I get two errors saying:
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding'
and
Cannot convert value of type 'String' to expected argument type 'Binding<String?>'
It might be an easy fix but as I am still learning I can't get my head around it.
Thanks
Thank you all,
This is what I found and seems to work:
TextField("Add content", text: Binding(
get: { self.topic.content ?? ""},
set: { self.topic.content = $0 } )
)

Editing a specific core data object in modal view

Progressing in my Core Data learning, I'm now stuck on the following :
I display a list of core data objects in ContentView.
If the user wants to edit one of them, he long-touches the item in the list which pops up a context sheet with and edit button. So far so good.
The edit Button summons a modal view on which the editing will take place.
In the Edit view, I start by fetching the correct item through its UUID property that I had stored in UserDefaults.
I am able to display the item's name, but I run into an issue with its date (the item has an "eventDate" Date property.
The app builds, I run it on my device, but it crashes as soon as I try to edit an item. An error is thrown in my EditView code when I instantiate the value to be displayed by a picker to the event's date :
Here's what happens inside the edit button :
Button(action: {
self.modalViewCaller = 1 // To tell the sheet which view to display
UserDefaults.standard.set(item.identNumber?.uuidString, forKey: kactiveEventUUID)
self.settingsModalIsPresented = true
})
{ Text("Edit entry")
Image(systemName: "globe")
}
And the way I fetch the event in EditView :
#Environment(\.managedObjectContext) var managedObjectContext
// We get the event to be edited through its UUID :
#FetchRequest(entity: Takeoffs.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Takeoffs.eventDate, ascending: false)],
predicate: NSPredicate(format: "identNumber == %#", UUID(uuidString: UserDefaults.standard.string(forKey: kactiveEventUUID)!)! as CVarArg)) var fetchedEvent: FetchedResults<Takeoffs>
// UserDefaults.standard.object(forKey: kactiveEventUUID) as! CVarArg)
#State var selectedDate = Date()
init() { // This sets "selectedDate" to the event's value for the date picker
_selectedDate = State(initialValue: fetchedEvent.first?.eventDate ?? Date()) // The underscore is used here
}
The project is available here if anyone has the courage : https://github.com/Esowes/RecentExp
Thanks for any help...
[Edit :] Trying the .didAppear solution suggested below, but can't seem to find a view that accepts .didAppear in my body :
var body: some View {
NavigationView {
Form {
HStack {
Text("Airport : ")
TextField(String(fetchedEvent.first?.airportName ?? ""), text: $airportNameTextfield)
.disabled(airportNameTextfield.count > 2) // To limit the textField to 3 chars (IATA code)
Button(action: {
self.airportNameTextfield = ""
}) {
Image(systemName: "clear")
}
} // END of Hstack
Picker("", selection: $typeSelectorIndex) {
ForEach(0 ..< types.count) { index in
Text(self.types[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
// Text("Selected type is: \(types[typeSelectorIndex])")
VStack {
Button(action: {
self.selectedDate = Date()
}) {
Text("Today")
}
DatePicker("",selection: $selectedDate, displayedComponents: .date)
.padding(30)
.labelsHidden()
}
} // END of Form
.navigationBarItems(
leading:
Button("Done") {
self.saveEdits()
self.presentationMode.wrappedValue.dismiss() // This dismisses the view
} // END of Button "Done"
)
.navigationBarTitle("Event edition")
} // END of Navigation View
} // END of some View
The fetchedEvent is not available yet at View.init call moment, so instead use it in .didAppear, like below
// ... remove that init at all
var body: some View {
Text("Any internal view here")
.onAppear {
// assigned fetched event data, here it is available
self.selectedDate = self.fetchedEvent.first?.eventDate ?? Date()
}
}

Resources