SwiftUI background thread - xcode

on my project I'm try to download some airport data via API call and display it on a list,
while this happening on my SwiftUI view I want to display a simple Circleloading animation.
To lunch the download I use a button that trigger the action and activate the animation using the #state (isSearching = false or true)
here my view :
import SwiftUI
struct AirportSearch: View {
#ObservedObject var dm : DataManager
#State var ICAOSearsh = ""
#State var isSearching = false
var body: some View {
ZStack {
backgroundGradiet.edgesIgnoringSafeArea(.all)
if self.isSearching == false{
LottieView(filename: "planeAnimation")
}
VStack{
CustomTextField(placeholder: Text(" Search ICAO").foregroundColor(.white), text: $ICAOSearsh)
.padding(.horizontal , 10)
.frame(height: 40.0)
.border(Color.black.opacity(5.0), width: 1)
\\ Button HERE________________________________________________________________
Button(action: {
self.isSearching = true
self.dm.searchAirportOnline(icaoCode: self.ICAOSearsh)
{
debugPrint("finito search")
self.isSearching = false
}
}) {
HStack{
Image(systemName: "magnifyingglass").foregroundColor(Color.white)
Text("Find your airport").foregroundColor(.white)
}
}
\\ button above HERE________________________________________________________
if isSearching == false{
List{
ForEach(self.dm.airportVector) { aeroporto in
VStack{
HStack{
Image(systemName: "airplane").foregroundColor(.red)
Text(aeroporto.aptShortName).bold()
Spacer()
Text(aeroporto.aptICAO)
Text("/")
Text(aeroporto.aptIATA)
}
HStack{
Text(aeroporto.countryCode)
Spacer()
Text(aeroporto.continent)
}.font(.system(size: 13))
}
.foregroundColor(.white)
}.padding(.horizontal)
}.onAppear {
UITableView.appearance().backgroundColor = UIColor.clear
UITableViewCell.appearance().backgroundColor = .clear
}
} else {
Spacer()
CircleLoading()
Spacer()
}
}
}
}
var backgroundGradiet: LinearGradient {
let gradient = LinearGradient(gradient: Gradient(colors: [Color.blue,Color.black]), startPoint: .topTrailing, endPoint: .bottomLeading)
return gradient
}
}
unfortunately when I press the button the animation freeze.
to solve this issue I'm try to use the DispatchQueue.global().async {} and DispatchQueue.main.async {} on my class DataManager, but I'm getting the warning :
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates```
here below my class :
import UIKit
import Combine
import SwiftUI
import CoreLocation
class DataManager: ObservableObject {
let objectWillChange = PassthroughSubject<Void,Never>()
static let shared = DataManager()
#Published var airportVector : [AirportModel] = []
{
didSet {
objectWillChange.send()
}
}
init() {
}
// MARK: - Search Online ICAO apt
func searchAirportOnline(icaoCode: String, closure : #escaping () ->()) {
DispatchQueue.global().async {
self.airportVector = [] // empty the vector to
// set the request apiKey
let headers : HTTPHeaders = [
"x-rapidapi-host": "aerodatabox.p.rapidapi.com",
"x-rapidapi-key": "18df152ef8msh5e365520639e47ep1fb24bjsn8e65c311e1f7"
]
let linkRequest = "https://aerodatabox.p.rapidapi.com/airports/icao/\(icaoCode)?withTime=true&withRunways=false"
// make request via AlamoFire
AF.request(linkRequest, method: .get, headers: headers)
.responseData { (responseData) in
switch responseData.result {
case .success(let value ) :
let json = JSON(value)
var airport = AirportModel(aptICAO: "", aptIATA: "", aptShortName: "", aptFullName: "", aptMunicipalName: "", locationLat: "", locationLon: "", countryName: "", countryCode: "", continent: "", timeZone: "", linkLiveATC: "", linkLinkFlightRadar: "", airportWebsite: "", currentTimeUTC: "", currentTimeLocal: "", runway: [RunwayModel(name1: RunwayModel.Runway(lengthM: "", lengthF: "", hasLighting: false, widthM: "", widthF: "", surface: "", isClosed: false, name: "", trueHdg: "", displacedThresholdM: "", displacedThresholdF: "", locationLat:0.0, locationLong: 0.0), name2: RunwayModel.Runway(lengthM: "", lengthF: "", hasLighting: false, widthM: "", widthF: "", surface: "", isClosed: false, name: "", trueHdg: "", displacedThresholdM: "", displacedThresholdF: "", locationLat:0.0, locationLong: 0.0))])
airport.aptICAO = json["icao"].stringValue
airport.aptIATA = json["iata"].stringValue
airport.aptShortName = json["shortName"].stringValue
airport.aptFullName = json["fullName"].stringValue
airport.aptMunicipalName = json["municipalityName"].stringValue
airport.locationLat = json["location"]["lat"].stringValue
airport.locationLon = json["location"]["lon"].stringValue
airport.countryName = json["country"]["name"].stringValue
airport.countryCode = json["country"]["code"].stringValue
airport.continent = json["continent"]["name"].stringValue
airport.timeZone = json["timeZone"].stringValue
airport.linkLiveATC = json["urls"]["liveAtc"].stringValue
airport.linkLinkFlightRadar = json["urls"]["flightRadar"].stringValue
airport.airportWebsite = json["urls"]["webSite"].stringValue
airport.currentTimeUTC = json["currentTime"]["utcTime"].stringValue
airport.currentTimeLocal = json["currentTime"]["localTime"].stringValue
self.runwayRequest(airportICAO: icaoCode) { (vettoreRunwayModel) in
airport.runway = vettoreRunwayModel
}
DispatchQueue.main.async { // ERROR LOOKS HERE..
self.airportVector.append(airport) // aggiungo ad airport vector l'aeroporto trovato
closure()
}
case.failure(let error) :
debugPrint(error)
debugPrint("cazzoo")
}
}
}
}
}
the mistake looks at self.airportVector.append because is a #Published var and I don't know how to solve the issue ..
thanks for the help,

Try this one
func searchAirportOnline(icaoCode: String, closure : #escaping () ->()) {
self.airportVector = [] // empty the vector to
// set the request apiKey
let headers : HTTPHeaders = [
"x-rapidapi-host": "aerodatabox.p.rapidapi.com",
"x-rapidapi-key": "18df152ef8msh5e365520639e47ep1fb24bjsn8e65c311e1f7"
]
let linkRequest = "https://aerodatabox.p.rapidapi.com/airports/icao/\(icaoCode)?withTime=true&withRunways=false"
// make request via AlamoFire
AF.request(linkRequest, method: .get, headers: headers)
.responseData { (responseData) in
switch responseData.result {
case .success(let value ) :
let json = JSON(value)
var airport = AirportModel(aptICAO: "", aptIATA: "", aptShortName: "", aptFullName: "", aptMunicipalName: "", locationLat: "", locationLon: "", countryName: "", countryCode: "", continent: "", timeZone: "", linkLiveATC: "", linkLinkFlightRadar: "", airportWebsite: "", currentTimeUTC: "", currentTimeLocal: "", runway: [RunwayModel(name1: RunwayModel.Runway(lengthM: "", lengthF: "", hasLighting: false, widthM: "", widthF: "", surface: "", isClosed: false, name: "", trueHdg: "", displacedThresholdM: "", displacedThresholdF: "", locationLat:0.0, locationLong: 0.0), name2: RunwayModel.Runway(lengthM: "", lengthF: "", hasLighting: false, widthM: "", widthF: "", surface: "", isClosed: false, name: "", trueHdg: "", displacedThresholdM: "", displacedThresholdF: "", locationLat:0.0, locationLong: 0.0))])
airport.aptICAO = json["icao"].stringValue
airport.aptIATA = json["iata"].stringValue
airport.aptShortName = json["shortName"].stringValue
airport.aptFullName = json["fullName"].stringValue
airport.aptMunicipalName = json["municipalityName"].stringValue
airport.locationLat = json["location"]["lat"].stringValue
airport.locationLon = json["location"]["lon"].stringValue
airport.countryName = json["country"]["name"].stringValue
airport.countryCode = json["country"]["code"].stringValue
airport.continent = json["continent"]["name"].stringValue
airport.timeZone = json["timeZone"].stringValue
airport.linkLiveATC = json["urls"]["liveAtc"].stringValue
airport.linkLinkFlightRadar = json["urls"]["flightRadar"].stringValue
airport.airportWebsite = json["urls"]["webSite"].stringValue
airport.currentTimeUTC = json["currentTime"]["utcTime"].stringValue
airport.currentTimeLocal = json["currentTime"]["localTime"].stringValue
self.runwayRequest(airportICAO: icaoCode) { (vettoreRunwayModel) in
DispatchQueue.main.async {
airport.runway = vettoreRunwayModel
}
}
DispatchQueue.main.async {
self.airportVector.append(airport) // aggiungo ad airport vector l'aeroporto trovato
closure()
}
case.failure(let error) :
debugPrint(error)
debugPrint("cazzoo")
}
}
}

Related

Drag&Drop works with URL, but not with String – why?

In my code the drag&drop function works fine with URL objects. But the exact same code for String objects does not. I have tried with countless casts and loadItem instead of loadObject ... no luck so far.
Can anyone help me here? It would be very much appreciated.
code for URL objects – Working
.onDrag {
return NSItemProvider(object: item.url as NSURL)
}
.onDrop(of: [UTType.url], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: URL.self) { url, error in
if let error = error { print(error.localizedDescription) }
if let url = url {
DispatchQueue.main.async {
self.array2.insert(Item(title: "new", url: url), at: 0)
}
}
}
return true
}
the same with String does not:
.onDrag {
return NSItemProvider(object: item.title as NSString)
}
.onDrop(of: [UTType.text], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: String.self) { string, error in
if let error = error { print(error.localizedDescription) }
if let string = string {
DispatchQueue.main.async {
self.array2.insert(Item(title: string, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}
full MRE code:
import SwiftUI
import UniformTypeIdentifiers
struct Item: Identifiable {
let id = UUID()
var title: String
var url: URL
}
struct ContentView: View {
#State var array1: [Item] = [
Item(title: "One", url: URL(string: "http://www.amazon.com")!),
Item(title: "Two", url: URL(string: "http://www.apple.com")!),
Item(title: "Three", url: URL(string: "http://www.example.com")!),
]
#State var array2: [Item] = []
#State var isDropping = false
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
ForEach(array1) { item in
Text(item.title)
Text(item.url.absoluteString).foregroundColor(.secondary)
// DRAG
.onDrag {
return NSItemProvider(object: item.url as NSURL) // WORKS
// return NSItemProvider(object: item.title as NSString) // DOES NOT WORK
}
}
}
.frame(maxWidth: .infinity)
Divider()
VStack(alignment: .leading) {
ForEach(array2) { item in
Text(item.title)
Text(item.url.absoluteString).foregroundColor(.secondary)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(isDropping ? .green : .gray)
// DROP with url/NSURL -- WORKS
.onDrop(of: [UTType.url], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: URL.self) { url, error in
if let error = error { print(error.localizedDescription) }
if let url = url {
DispatchQueue.main.async {
self.array2.insert(Item(title: "new", url: url), at: 0)
}
}
}
return true
}
// DROP with text/NSString -- DOES NOT WORK
.onDrop(of: [UTType.text], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: String.self) { string, error in
if let error = error { print(error.localizedDescription) }
if let string = string {
DispatchQueue.main.async {
self.array2.insert(Item(title: string, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}
}
}
}
As always thanks to #Asperi and his answer here:
SwiftUI: Not getting dropped NSString value in DropDelegate
This now works:
.onDrop(of: [UTType.utf8PlainText], isTargeted: $isDropping) { providers in
_ = providers.first?.loadItem(forTypeIdentifier: "public.utf8-plain-text") { data, error in
if let error = error { print(error.localizedDescription) }
if let data = data as? Data {
DispatchQueue.main.async {
let string = NSString(data: data, encoding: 4) ?? "failed"
print(string)
self.array2.insert(Item(title: string as String, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}

SwiftUI(Core Data) - Xcode Preview stops, but does not crash

Although Xcode does not give me any information, I assumed that it is related to Core Data because it started since I changed codes related to Core Data.
My Code
I have many codes in my project, and I cannot show you the whole code because of word limit. But I can show the core of my project.
ContentView - includes two tab views(VocabularyFolderView and QuizHome View)
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
VocabularyFolderView()
.tabItem {
Label("Vocabulary", systemImage: "list.dash")
}
QuizHomeView()
.tabItem {
Label("Quiz", systemImage: "questionmark.app")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
//ForEach(["iPhone SE (3nd generation)", "iPhone 13", "iPad Pro (11-inch) (1st generation)", "iPad Air (3rd generation)", "iPad Air (4th generation)"], id: \.self) { deviceName in
//ContentView()
//.environment(\.managedObjectContext, PersistenceController.VocabularyFolder_Preview.container.viewContext)
//.previewDevice(PreviewDevice(rawValue: deviceName))
//.previewDisplayName(deviceName)
//}
ContentView()
.environment(\.managedObjectContext, PersistenceController.VocabularyFolder_Preview.container.viewContext)
.previewInterfaceOrientation(.portrait)
}
}
VocabularyFolderView - It is a NavigationView that contains a list of Core Data entities
import SwiftUI
import CoreData
struct VocabularyFolderView: View {
#Environment(\.managedObjectContext) private var managedObjectContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \FolderStruct.name, ascending: true)],
animation: .default)
private var folders: FetchedResults<FolderStruct>
#State private var showAddView: Bool = false
#State private var showAddErrorAlert: Bool = false
#State private var showDeleteErrorAlert: Bool = false
#State private var showFavoritesOnly = false
var filteredFolders: [FolderStruct] {
folders.filter { folder in
(!showFavoritesOnly || folder.starred)
}
}
var body: some View {
NavigationView {
List {
Section {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
}
Section {
ForEach(filteredFolders) { folder in
NavigationLink {
VocabularyListView(parentFolder: folder)
} label: {
HStack {
Image(systemName: "folder")
.foregroundColor(.accentColor)
.listRowSeparator(.hidden)
Text(folder.name ?? "")
Spacer()
Text("\(folder.childlists!.count)")
.foregroundColor(.secondary)
if folder.starred {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
} else {
Image(systemName: "star")
.foregroundColor(.gray)
}
}
}
.swipeActions(edge: .leading) {
Button {
deleteFolder(folder: folder)
} label: {
Label("Pin", systemImage: "pin.fill")
}
.tint(.orange)
}
.swipeActions(edge:. trailing, allowsFullSwipe: false) {
Button(role: .destructive) {
deleteFolder(folder: folder)
} label: {
Label("Delete", systemImage: "trash.fill")
}
}
}
}
}
.navigationTitle("Folders")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem(placement: .bottomBar) {
HStack {
Button(action: addItem) {
Label("Add Item", systemImage: "folder.badge.plus")
}
Spacer()
}
}
}
.sheet(isPresented: $showAddView) {
AddFolderView()
}
//alert(isPresented: $showAddView,
// TextAlert(title: "New Folder", message: "Message") { result in
// if let text = result {
// // Text was accepted
// } else {
// // The dialog was cancelled
// }
// })
.alert("Error while deleting a word", isPresented: $showDeleteErrorAlert) {
Button("OK", role: .cancel) { }
}
.alert("Error while adding a word", isPresented: $showAddErrorAlert) {
Button("OK", role: .cancel) { }
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
private func addItem() {
showAddView = true
}
private func deleteFolder(folder: FolderStruct) {
managedObjectContext.delete(folder)
do {
try managedObjectContext.save()
} catch {
showDeleteErrorAlert = true
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
struct VocabularyFolderView_Previews: PreviewProvider {
static var previews: some View {
VocabularyFolderView().environment(\.managedObjectContext, PersistenceController.VocabularyFolder_Preview.container.viewContext)
}
}
QuizHomeView
import SwiftUI
import CoreData
struct QuizHomeView: View {
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \FolderStruct.name, ascending: true)],
animation: .default)
private var folders: FetchedResults<FolderStruct>
#State private var showingSheet = false
#State private var quizType = "단어 뜻 맞추기"
#State private var folderSelection: FolderStruct?
#State private var listSelection = "단어장 선택"
let quizTypes = ["단어 뜻 맞추기", "원래 단어 맞추기"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Quiz Type", selection: $quizType) {
ForEach(quizTypes, id: \.self) {
Text($0)
}
}
.pickerStyle(.segmented)
}
Section {
Picker("폴더 선택", selection: $folderSelection) {
ForEach(folders) { (folder: FolderStruct) in
Text(folder.name!)
}
}
//Picker("단어장 선택", selection: $listSelection) {
// ForEach(listSelections, id: \.self) {
// Text($0)
// }
//}
}
Section {
Button("시작하기") {
showingSheet.toggle()
}
.disabled(folderSelection == nil || listSelection.isEmpty || listSelection == "단어장 선택")
}
}
.navigationTitle("퀴즈")
.sheet(isPresented: $showingSheet) {
QuizPlayView()
}
}
}
}
struct QuizHomeView_Previews: PreviewProvider {
static var previews: some View {
QuizHomeView()
}
}
Persistence.swift
import CoreData
import Foundation
struct PersistenceController {
static let shared = PersistenceController()
static var VocabularyFolder_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
var previewString = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
for i in previewString {
let newFolder = FolderStruct(context: viewContext)
newFolder.name = i
newFolder.starred = true
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var AddFolder_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let newFolder = FolderStruct(context: viewContext)
newFolder.name = "Example Folder"
newFolder.starred = true
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var VocabularyList_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
var previewString = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
for i in previewString {
let parentFolder = FolderStruct(context: viewContext)
parentFolder.name = i
parentFolder.starred = true
let newList = ListStruct(context: viewContext)
newList.name = i
newList.starred = true
newList.parentfolder = parentFolder
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var AddList_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let parentFolder = FolderStruct(context: viewContext)
parentFolder.name = "Example Folder"
parentFolder.starred = true
let newList = ListStruct(context: viewContext)
newList.name = "Example List"
newList.starred = true
newList.parentfolder = parentFolder
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var Vocabulary_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let previewString = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
for i in previewString {
let parentList = ListStruct(context: viewContext)
parentList.name = i
parentList.starred = true
let newVocabulary = VocabularyStruct(context: viewContext)
newVocabulary.word = i
newVocabulary.meaning = i
newVocabulary.starred = true
newVocabulary.parentlist = parentList
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var AddVocabulary_Preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let parentList = ListStruct(context: viewContext)
parentList.name = "Example List"
parentList.starred = true
let newVocabulary = VocabularyStruct(context: viewContext)
newVocabulary.word = "Example Word"
newVocabulary.meaning = "Example Meaning"
newVocabulary.starred = true
newVocabulary.parentlist = parentList
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var FolderStruct_Preview: FolderStruct {
let folder = FolderStruct()
folder.name = "Example Folder"
folder.starred = true
return folder
}
static var ListStruct_Preview: ListStruct {
let list = ListStruct()
list.name = "Example List"
list.starred = true
list.parentfolder = FolderStruct_Preview
return list
}
static var VocabularyStruct_Preview: VocabularyStruct {
let vocabulary = VocabularyStruct()
vocabulary.word = "Example Word"
vocabulary.meaning = "Example Meaning"
vocabulary.starred = true
vocabulary.parentlist = ListStruct_Preview
return vocabulary
}
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Vocabulary")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}

How to remove Top Safe Area in Zstack With ScrollView Image SwiftUI

I'm trying to remove top safe area. Is there any way to remove top safe area from top and image?
Code:-
struct ContentView22: View {
#State private var showDialog = false
var body: some View {
ZStack {
ScrollView {
VStack {
Image("CentrImg.jpeg")
.resizable()
.scaledToFill()
.frame(width:UIScreen.screenWidth,height: 180, alignment: .center)
.clipped()
.ignoresSafeArea()
.edgesIgnoringSafeArea(.top)
VStack(alignment:.leading,spacing:25) {
Text("Some text")
.onTapGesture {
showDialog = true
}
}
}
}
.alert(isPresented: $showDialog,TextAlert(title: "Title",message: "Message") { result in
print(result as Any)
if let _ = result {
} else {
}
})
}.edgesIgnoringSafeArea(.top)
.background(Color.red)
.foregroundColor(.white)
.navigationBarHidden(true)
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("", displayMode: .inline)
}
}
Alert Control Class:-
import SwiftUI
import Combine
public struct TextAlert {
public var title: String // Title of the dialog
public var message: String // Dialog message
public var placeholder: String = "" // Placeholder text for the TextField
public var accept: String = "OK" // The left-most button label
public var cancel: String? = "Cancel" // The optional cancel (right-most) button label
public var secondaryActionTitle: String? = nil // The optional center button label
public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog
public var secondaryAction: (() -> Void)? = nil // Triggers when the optional center button is tapped
}
extension UIAlertController {
convenience init(alert: TextAlert) {
self.init(title: alert.title, message: alert.message, preferredStyle: .alert)
addTextField {
$0.placeholder = alert.placeholder
$0.returnKeyType = .done
}
if let cancel = alert.cancel {
addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
alert.action(nil)
})
}
if let secondaryActionTitle = alert.secondaryActionTitle {
addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in
alert.secondaryAction?()
}))
}
let textField = self.textFields?.first
addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
alert.action(textField?.text)
})
}
}
struct AlertWrapper<Content: View>: UIViewControllerRepresentable {
#Binding var isPresented: Bool
let alert: TextAlert
let content: Content
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIHostingController<Content> {
UIHostingController(rootView: content)
}
final class Coordinator {
var alertController: UIAlertController?
init(_ controller: UIAlertController? = nil) {
self.alertController = controller
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<AlertWrapper>) {
uiViewController.rootView = content
if isPresented && uiViewController.presentedViewController == nil {
var alert = self.alert
alert.action = {
self.isPresented = false
self.alert.action($0)
}
context.coordinator.alertController = UIAlertController(alert: alert)
uiViewController.present(context.coordinator.alertController!, animated: true)
}
if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController {
uiViewController.dismiss(animated: true)
}
}
}
extension View {
public func alert(isPresented: Binding<Bool>, _ alert: TextAlert) -> some View {
AlertWrapper(isPresented: isPresented, alert: alert, content: self)
}
}
Output with alert code
Output without alert code:-
Can someone please explain to me how to remove top safe area from image with alert code, I've tried to implement by above but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I removed your Alert code. You can do the same with a much simpler function.
Value
#State var testText: String = ""
Alert Func
func alertView() {
let alert = UIAlertController(title: "Test", message: "Test Message", preferredStyle: .alert)
alert.addTextField { (testTextField) in
testTextField.placeholder = "Test TextField"
}
let okButton = UIAlertAction(title: "OK", style: .default) { (_) in
self.testText = alert.textFields?[0].text ?? ""
}
let cancellButton = UIAlertAction(title: "Cancel", style: .destructive) { (_) in
}
alert.addAction(okButton)
alert.addAction(cancellButton)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: {
})
}
Using:
Text("Some text")
.onTapGesture {
alertView()
}

Pre-enter text in a textfield in SwiftUI

I was able to solve my problem i had earlier on how to update data in Firestore via UI. Now the next problem:
I would like to have my Firebase-Stored Data pre-entered in a text field so you can just !edit! the text in the fields you want instead of writing all data new in every single field. As you can see, the only thing i was able to do, is getting the stored data in the text field as placeholder, which means you have to re-write all data in case you want to update just 1 field.
CODE:
import SwiftUI
import Firebase
struct ContentViewVier: View {
#ObservedObject var dataDrei = getDataZwei()
var body: some View {
NavigationView{
ZStack(alignment: .top){
GeometryReader{_ in
// Home View....
Text("Bitte Seriennummer eingeben").foregroundColor(.black)
}.background(Color("FarbeSeriennummerStartbildschirm").edgesIgnoringSafeArea(.all))
CustomSearchBarEdit(dataZwei: self.$dataDrei.datasZwei).padding(.top)
}.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
struct CustomSearchBarEdit : View {
#State var txt = ""
#Binding var dataZwei : [dataTypeZwei]
var body : some View{
VStack(spacing: 0){
HStack{
TextField("Nach Seriennummer suchen", text: self.$txt).opacity(100).foregroundColor(.black)
if self.txt != ""{
Button(action: {
self.txt = ""
}) {
Text("Abbrechen")
}
.foregroundColor(.black)
}
}.padding()
if self.txt != ""{
if
self.dataZwei.filter({$0.sn.lowercased() .contains(self.txt.lowercased())}).count == 0 {
Text("Es wurde kein Gerät mit dieser Seriennummer gefunden").foregroundColor(Color.red.opacity(0.6)).padding()
}
else{
List(self.dataZwei.filter{$0.sn.contains(self.txt.lowercased())}){j in
NavigationLink(destination: DetailZwei(data: j)) {
Text(j.sn)
}
Text(j.typ)
}.frame(height: UIScreen.main.bounds.height / 5)
}
}
}.background(Color.white)
.padding()
}
}
class getDataZwei : ObservableObject{
#Published var datasZwei = [dataTypeZwei]()
init() {
let db = Firestore.firestore()
db.collection("Geräte").getDocuments { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documents{
let id = i.documentID
let sn = i.get("Seriennummer") as! String
let objekt = i.get("Objekt") as! String
let str = i.get("Strasse") as! String
let pos = i.get("Position") as! String
let typ = i.get("Gerätetyp") as! String
let ida = i.get("Installation")as! String
let lg = i.get("LeasingOderGekauft")as! String
let la = i.get("LeasingAblaufdatum")as! String
let ga = i.get("GarantieAblaufdatum")as! String
let nr = i.get("Hausnummer")as! String
let plz = i.get("Postleitzahl")as! String
let ort = i.get("Ort")as! String
let vp = i.get("Verantwortlich")as! String
let tel = i.get("Telefonnummer")as! String
let zusatz = i.get("Zusätzlich")as! String
let ed = i.get("EingetragenDurch")as! String
let ldvds = i.get("LieferungBeiVDS")as! String
self.datasZwei.append(dataTypeZwei(id: id, sn: sn, pos: pos, typ: typ, ida: ida, lg: lg, la: la, ga: ga, objekt: objekt, str: str, nr: nr, plz: plz, ort: ort, vp: vp, tel: tel, zusatz: zusatz, ed: ed, ldvds: ldvds))
}
}
}
}
struct dataTypeZwei : Identifiable, Codable {
var id: String? = UUID().uuidString
var sn : String
var pos : String
var typ : String
var ida : String
var lg : String
var la : String
var ga : String
var objekt : String
var str : String
var nr : String
var plz : String
var ort : String
var vp : String
var tel : String
var zusatz : String
var ed : String
var ldvds : String
enum CodingKeys: String, CodingKey {
case sn = "Seriennummer"
case objekt = "Objekt"
case str = "Strasse"
case nr = "Hausnummer"
case ort = "Ort"
case vp = "Verantwortlich"
case tel = "Telefonnummer"
case pos = "Position"
case typ = "Gerätetyp"
case ida = "Installation"
case lg = "LeasingOderGekauft"
case la = "LeasingAblaufdatum"
case ga = "GarantieAblaufdatum"
case zusatz = "Zusätzlich"
case plz = "Postleitzahl"
case ed = "EingetragenDurch"
case ldvds = "LieferungBeiVDS"
}
}
struct DetailZwei : View {
var data : dataTypeZwei
#State var viewModel = GerätEditieren()
#State var serie: String? = nil
#Environment(\.presentationMode) var presentationMode
var body : some View {
NavigationView {
ScrollView {
Group {
Section(header: Text("Gerät")) {
Text("Seriennummer")
TextField(data.sn, text: $viewModel.gerät.sn).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Objekt")
TextField(data.objekt, text: $viewModel.gerät.objekt).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Gerätetyp")
TextField(data.typ, text: $viewModel.gerät.typ).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Geräteposition")
TextField(data.pos, text: $viewModel.gerät.pos).textFieldStyle(RoundedBorderTextFieldStyle())
}
Group {
Text("Installationsdatum")
TextField(data.ida, text: $viewModel.gerät.ida).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Leasing oder Gekauft?")
TextField(data.lg, text: $viewModel.gerät.lg).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Leasing")
TextField(data.la, text: $viewModel.gerät.la).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ablaufdatum Garantie")
TextField(data.ga, text: $viewModel.gerät.ga).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Adresse")) {
Text("Strasse")
TextField(data.str, text: $viewModel.gerät.str).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Hausnummer")
TextField(data.nr, text: $viewModel.gerät.nr).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Postleitzahl")
TextField(data.plz, text: $viewModel.gerät.plz).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Ort")
TextField(data.ort, text: $viewModel.gerät.ort).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Kontakt")) {
Text("Ansprechperson")
TextField(data.vp, text: $viewModel.gerät.vp).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Telefonnummer")
TextField(data.tel, text: $viewModel.gerät.tel).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("VDS")) {
Text("Eingetragen durch")
TextField(data.ed, text: $viewModel.gerät.ed).textFieldStyle(RoundedBorderTextFieldStyle())
Text("Lieferdatum VDS")
TextField(data.ldvds, text: $viewModel.gerät.ldvds).textFieldStyle(RoundedBorderTextFieldStyle())
}
Section(header: Text("Zusätzliche Informationen")) {
Text("Zusätzliche Informationen")
TextField(data.zusatz, text: $viewModel.gerät.zusatz).textFieldStyle(RoundedBorderTextFieldStyle())
}
}.padding()
.navigationBarTitle("Gerät bearbeiten", displayMode: .inline)
.navigationBarItems(leading: Button(action: { self.handleCancelTapped() }, label: {
Text("Abbrechen")
}),
trailing: Button(action: { self.handleDoneTapped() }, label: {
Text("Speichern")
})
// .disabled(!viewModel.modified)
)
}
}
}
func handleCancelTapped() {
dismiss()
}
func handleDoneTapped() {
let db = Firestore.firestore()
let docRef = db.collection("Geräte").document(data.id!)
print("setting data")
docRef.updateData(["Seriennummer": "\(viewModel.gerät.sn)", "Objekt": "\(viewModel.gerät.objekt)", "Strasse": "\(viewModel.gerät.str)", "Hausnummer": "\(viewModel.gerät.nr)", "Ort": "\(viewModel.gerät.ort)", "Verantwortlich": "\(viewModel.gerät.vp)", "Telefonnummer": "\(viewModel.gerät.tel)", "Position": "\(viewModel.gerät.pos)", "Gerätetyp": "\(viewModel.gerät.typ)", "Installation": "\(viewModel.gerät.ida)", "LeasingOderGekauft": "\(viewModel.gerät.lg)", "LeasingAblaufdatum": "\(viewModel.gerät.la)", "GarantieAblaufdatum": "\(viewModel.gerät.ga)", "Zusätzlich": "\(viewModel.gerät.zusatz)", "Postleitzahl": "\(viewModel.gerät.plz)", "EingetragenDurch": "\(viewModel.gerät.ed)", "LieferungBeiVDS": "\(viewModel.gerät.ldvds)"]){ (error) in
if let error = error {
print("error = \(error)")
} else if self.viewModel.gerät.sn == "" {
self.viewModel.gerät.sn = self.data.sn
}
else {
print("data uploaded successfully")
}
}
dismiss()
}
func dismiss() {
presentationMode.wrappedValue.dismiss()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentViewVier()
}
}
This belong to your viewModel to initialize the Published values with the exact value.
Another option is to set the State variables, which you pass to your TextField with the actual value.
#State var vp : String
init(data: Data) { 
self._vp = State(initialValue: data.vp) //<< here set the value from Firebase
}
TextField(data.vp, text: $vp).textFieldStyle(RoundedBorderTextFieldStyle())

View update problem with UITextField integration into SwiftUI

In the following code, I cannot seem to get the FORM view to update when I change the value (and therefore state) of some of the fields, when using a CustomTextField. I imagine that the problem is in the coordination between the CustomTextField and SwiftUI, but I get the values when I do the calculations (below), but I can't get the updated values to display in the relevant UITextFields onscreen.
Can anyone spot my error? Any ideas would be greatly appreciated.
Thanks a lot.
Chris
struct CustomTextField: UIViewRepresentable {
var tag:Int = 0
var placeholder:String?
var keyboardType:UIKeyboardType?
var textAlignment:NSTextAlignment?
#Binding var text: String
var onChange: (()->Void?)?
func makeCoordinator() -> Coordinator {
Coordinator(text: $text, onChange: onChange)
}
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let tmpView = UITextField()
tmpView.tag = tag
tmpView.delegate = context.coordinator as UITextFieldDelegate
tmpView.placeholder = placeholder
tmpView.textAlignment = textAlignment ?? .left
tmpView.keyboardType = keyboardType ?? .default
tmpView.addDoneButtonOnKeyboard()
return tmpView
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
class Coordinator : NSObject, UITextFieldDelegate {
#Binding var text: String
var onChange:(()->Void?)?
init(text: Binding<String>, onChange: (()->Void?)?) {
self._text = text
self.onChange = onChange
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentTag = textField.tag as Int? {
if currentTag == 1 {
if string.count > 0 /*&& !textField.text!.contains(".")*/ {
let tmpCents = textField.text?.replacingOccurrences(of: ".", with: "") ?? ""
let cents = Int( tmpCents + string) ?? 0
if cents == 0 {
textField.text = "0.00"
} else {
let dols = Float(cents)/Float(100)
textField.text = String(format: "%0.2f", dols)
}
self.text = textField.text!
return false
}
}
}
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
text = proposedValue
}
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
self.text = textField.text ?? ""
return true
}
func textFieldDidChange(_ textField: UITextField) {
self.text = textField.text ?? ""
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.onChange?()
textField.resignFirstResponder()
}
}
}
struct DetailView: View {
#EnvironmentObject var log: GasLog
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State var amount = ""
#State var pickedDate = Date()
#State var kilometers = ""
#State var editingAmount = false
#State var litres = ""
#State var gasPrice = ""
#State var showAlert = false
#State var errorMessage = ""
#State var reloadContent = false
var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -10, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 10, to: Date())!
return min...max
}
var body: some View {
VStack {
HStack {
Text("ADD GAS PURCHASE")
.font(defaultSectionFont)
}
Form {
DatePicker(selection: $pickedDate, displayedComponents: .date, label: {
Text("PURCHASE DATE").font(defaultFormFont)
} )
HStack {
Text("AMOUNT").font(defaultFormFont)
Spacer()
CustomTextField(tag: 1,
placeholder: "purchase amount",
keyboardType: .numberPad,
textAlignment: .right,
text: $amount,
onChange: nil)
}
HStack {
Text("LITRES").font(defaultFormFont)
Spacer()
CustomTextField(tag: 1, placeholder: "litres purchased", keyboardType: .numberPad,
textAlignment: .right, text: $litres, onChange: self.calcValues)
}
HStack {
Text("FUEL PRICE").font(defaultFormFont)
Spacer()
CustomTextField(tag: 1, placeholder: "fuel price", keyboardType: .numberPad,
textAlignment: .right, text: $gasPrice, onChange: self.calcValues)
}
HStack {
Text("KILOMETERS ON CAR").font(defaultFormFont)
Spacer()
CustomTextField(tag: 0, placeholder: "kilometers", keyboardType: .numberPad,
textAlignment: .right, text: $kilometers, onChange: nil)
}
}
HStack {
Spacer()
Button(action: {
self.cancelRecord()
}, label: {
Image(systemName: "return")
})
.padding()
.overlay(
RoundedRectangle(cornerRadius: CGFloat(8.0))
.stroke(Color.gray, lineWidth: CGFloat(2.0))
)
Spacer()
Button(action: {
self.commitRecord()
}, label: {
Image(systemName: "plus.square")
})
.padding()
.overlay(
RoundedRectangle(cornerRadius: CGFloat(8.0))
.stroke(Color.gray, lineWidth: CGFloat(2.0))
)
Spacer()
}
.padding()
.background(toolbarBackgroundColor)
}.alert(isPresented: $showAlert) {
Alert(title: Text("Error"), message: Text(self.errorMessage))
}
}
func calcValues() -> Void {
if !self.amount.isEmpty {
switch (!self.gasPrice.isEmpty, !self.litres.isEmpty) {
case (true, false) :
self.litres = String(format: "%0.2f", Float(self.amount)! / Float(self.gasPrice)!)
self.reloadContent = true
case (false, true) :
self.gasPrice = String(format: "%0.2f", Float(self.amount)! / Float(self.litres)!)
self.reloadContent = true
default :
self.reloadContent = false
}
}
}
func commitRecord() {
let log = GasLog.shared()
if self.amount.isEmpty || Float(self.amount) == 0.0 {
errorMessage = "Value of AMOUNT is invalid. Please re-enter."
showAlert = true
} else {
self.dismiss()
log.addLogItem(date: self.pickedDate,
amount: (self.amount.isEmpty ? 0.00 : Float(self.amount)!),
kilometers: (self.kilometers.isEmpty ? nil : Int(self.kilometers)),
gasPrice: (self.gasPrice.isEmpty ? nil : Float(self.gasPrice)),
litres: (self.litres.isEmpty ? nil : Float(self.litres)))
}
}
func cancelRecord() {
self.dismiss()
}
func dismiss() {
self.presentationMode.wrappedValue.dismiss()
}
}
Add uiView.text = text to updateUIView:
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
uiView.text = text // add this
}

Resources