Thread 14 SwiftUI - Help to call an API and log in - swift

Hello fellow Swift Coders. I'm a beginner, and i'm trying to call a web API then log in to have access to a new view.
This is my code, everything seems running well (before i try to put the login + pass)
import SwiftUI
import Foundation
struct ContentView: View {
#State private var username = ""
#State private var password = ""
#State private var loginSuccessful = false
var body: some View {
NavigationView {
ZStack {
Color("primaryColor").ignoresSafeArea()
VStack {
HStack(alignment: .top) {
Text("FOLD")
.font(.system(size: 50))
.foregroundColor(Color(.white))
.padding(.bottom, 80)
}
ZStack{
Rectangle()
.frame(width: 345, height: 400)
.cornerRadius(25)
.foregroundColor(Color("secondaryColor"))
.shadow(radius: 10, x: 10, y: 3)
VStack(alignment: .leading) {
Text("Se connecter")
.font(.system(size: 20))
.foregroundColor(.white)
.bold()
.padding(.bottom,50)
VStack(alignment: .leading) {
Text("Email")
.font(.callout)
.bold()
.foregroundColor(.white)
HStack {
TextField("Enter email", text: $username)
.padding()
.font(.system(size: 14))
.foregroundColor(Color("primaryColor"))
.frame(width: 270, height: 50)
.background(.white)
.cornerRadius(10)
}
}
.frame(width: 270, height: 60)
.padding(.bottom, 30)
VStack(alignment: .leading) {
Text("Password")
.font(.callout)
.bold()
.foregroundColor(.white)
HStack {
SecureField("Password", text: $password)
.padding()
.font(.system(size: 14))
.foregroundColor(Color("primaryColor"))
.frame(width: 270, height: 50)
.background(.white)
.cornerRadius(10)
}
}
.frame(width: 270, height: 60)
.padding(.bottom, 50)
Button(action: {
logIn(username: username, password: password)
}) {
Text("Log in")
}
.font(.headline)
.foregroundColor(.white)
.frame(width: 270,height: 40)
.background(Color("buttonColor"))
.cornerRadius(10)
}
.sheet(isPresented: $loginSuccessful) {
QRscan()
}
}
}
Spacer()
}
}
}
func logIn(username: String, password: String) {
let url = URL(string: "https://foldlab.io:8081/loginScanAccount")!
let body = ["username": username, "password": password]
let jsonData = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
// Check if the login was successful
if json["status"] as! String == "success" {
DispatchQueue.main.async {
self.loginSuccessful = true
}
} else {
print("Error: Invalid username or password.")
}
}
}
task.resume()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
The thing is, when i'm trying on the simulator or the preview, its crashing. And i have an error i've never seen.
What should i do ?
Thread 14: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0., NSJSONSerializationErrorIndex=0}
Thanks a lot.
I tried to check on SOF, but i didnt really understand the answers

Related

How to properly use SwiftUI's Photo Picker to upload image?

I am using SwiftUI's native image picker and selecting/loading images works great. What I can't figure out is how to call the selected image when uploading the image to firebase. In my UploadPostView, I can't figure out what to call for the image in viewModel.uploadPost(caption: caption, image: <??>)) I've tried calling the selectedImage but I get the error that it is not a UIImage.. I thought I had done the work to convert the data in to a UIImage in my imagepicker. Anyways, I am new to Swift and I feel like I'm missing something obvious so any help is appreciated!
UploadPostView
struct UploadPostView: View {
#StateObject var imagePicker = ImagePicker()
#State private var caption = ""
#Environment(\.presentationMode) var mode
#ObservedObject var viewModel = UploadPostViewModel()
var body: some View {
NavigationView {
VStack {
HStack(alignment: .top, spacing: 16) {
if let user = AuthViewModel.shared.currentUser {
KFImage(URL(string: user.profileImageUrl ?? ""))
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.cornerRadius(10)
}
TextField("Enter your post here", text: $caption, axis: .vertical)
.lineLimit(5...10)
}.padding()
Spacer()
if let image = imagePicker.image {
HStack {
ZStack (alignment: .top) {
image
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.cornerRadius(4)
Button {
self.imagePicker.image = nil
} label: {
Image(systemName: "xmark")
.frame(width: 24, height: 24)
.foregroundColor(.white)
.padding(4)
.background(.black)
.clipShape(Circle())
}
.offset(x: 45, y: -15)
}
Spacer()
}
.padding()
}
HStack (spacing: 24) {
Text("Add to your post")
.foregroundColor(Color(.darkGray))
Spacer()
PhotosPicker(selection: $imagePicker.imageSelection) {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}.foregroundColor(Color(.darkGray))
Button {
} label: {
Image(systemName: "at")
.resizable()
.scaledToFit()
.frame(width: 22, height: 22)
}.foregroundColor(Color(.darkGray))
}
.padding()
}
.onReceive(viewModel.$didUploadPost) { success in
if success {
mode.wrappedValue.dismiss()
}
}
.navigationTitle("Create Post")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
mode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.foregroundColor(Color(.darkGray))
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
viewModel.uploadPost(caption: caption, image: image))
} label: {
Text("Post")
.font(.system(size: 18))
.bold()
}
}
}
}
}
}
UploadPostViewModel
class UploadPostViewModel: ObservableObject {
#Published var didUploadPost = false
#Published var didDeletePost = false
let service = PostService()
func uploadPost(caption :String, image: UIImage?) {
service.uploadPost(caption: caption, image: image) { success in
if success {
//dismiss screen
self.didUploadPost = true
} else {
// show error message to user..
}
}
}
}
ImagePicker
#MainActor class ImagePicker: ObservableObject {
#Published var image: UIImage?
#Published var imageSelection: PhotosPickerItem? {
didSet {
if let imageSelection {
Task {
try await loadTransferable(from: imageSelection)
}
}
}
}
func loadTransferable(from imageSelection: PhotosPickerItem?) async throws {
do {
if let data = try await imageSelection?.loadTransferable(type: Data.self) {
if let uiImage = UIImage(data: data) {
self.image = uiImage
}
}
} catch {
print(error.localizedDescription)
image = nil
}
}
}

Construct View Like Pedantix SwiftUI

The problem is quite simple. I want to build something like Pedantix https://cemantix.certitudes.org/pedantix in SwiftUI.
I've this already :
So, I try to have my RoundedRectangle overlay to totally hide my text. And I want blocks to go at the line if needed, etc. I tried LazyHGrid (actually this), LazyVGrid, custom grid. But no results ...
import SwiftUI
struct Word: Identifiable, Equatable {
var id = UUID()
var text: String
var isFramed: Bool
var isTouched: Bool
}
struct ContentView: View {
#EnvironmentObject var service: Service
let rows = [
GridItem(.adaptive(minimum: 30)),
]
var body: some View {
GeometryReader { gr in
ScrollView {
VStack(alignment: .leading) {
HStack {
Spacer()
Image(systemName: "arrow.counterclockwise.circle")
.resizable()
.scaledToFit()
.frame(width: 24)
.onTapGesture {
service.loadRandomMovies(page: 1, completion: { _ in
service.loadMovie(id: service.randomMovieId ?? 0, completion: { _ in })
service.loadCredits(id: service.randomMovieId ?? 0, completion: { _ in })
})
}
}
HStack {
VStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: 150, height: 250)
}
.padding()
VStack(alignment: .center) {
customTextView(with: service.frame(in: .title))
.padding(.bottom, 8)
customTextView(with: service.frame(in: .genres))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.movie?.releaseDate ?? "")")
.font(.title3)
.padding(.bottom, 8)
if service.movie?.tagline != "" {
Text("\"\(service.movie?.tagline ?? "")\"")
.font(.title3)
.padding(.bottom, 8)
.frame(alignment: .center)
}
customTextView(with: service.frame(in: .overview))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.credits?.cast.map({ $0.name }).joined(separator: " - ") ?? "")")
.fontWeight(.bold)
}
}
}
.padding()
}
.frame(width: gr.size.width)
}
}
}
extension ContentView {
#ViewBuilder
func customTextView(with words: [Word]) -> some View {
VStack {
LazyHGrid(rows: rows, spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text("\(word.text)")
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 4))
.overlay {
if word.isTouched {
Text("\(word.text.count)")
.foregroundColor(Color.cyan)
}
}
)
}
return AnyView(Text(word.text))
}
}
}
}
}
Do you think you could post your code so that we can see what you have done?

Getting error "Cannot find 'previewWeather' in scope"

I am a beginner developer and I am getting this error "Cannot find 'previewWeather' in scope" when I have the "previewWeather" variable in my "Model Data" file?
struct WeatherView: View {
var weather: ResponseBody
var body: some View {
ZStack(alignment: .leading) {
VStack {
VStack(alignment: .leading, spacing: 5) {
Text(weather.name)
.bold().font(.title)
Text("Today,\(Date().formatted(.dateTime.month() .day().hour().minute()))")
.fontWeight(.light)
}
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
VStack {
HStack {
VStack(spacing: 20) {
Image(systemName: "sun.max.fill")
.font(.system(size: 40))
Text(weather.weather[0].main)
}
.frame(width: 150, alignment: .leading)
Spacer()
Text(weather.main.feels_like.roundDouble() + "°")
.font(.system(size: 100))
.fontWeight((.bold))
.padding()
}
Spacer()
.frame(height: 80)
AsyncImage(url: URL(string: "https://cdn.pixabay.com/photo/2020/01/24/21/33/city-4791269_960_720.png")) { image in image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 350)
} placeholder: {
ProgressView()
}
Spacer()
}
.frame(maxWidth: .infinity)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
VStack {
Spacer()
VStack(alignment: .leading, spacing: 20) {
Text("Weather now")
.bold().padding(.bottom)
HStack {
WeatherRow(logo: "thermometer", name: "Min temp", value: (weather.main.temp_min.roundDouble() + "°"))
Spacer()
WeatherRow(logo: "thermometer", name: "Max temp", value: (weather.main.temp_max.roundDouble() + "°"))
}
HStack {
WeatherRow(logo: "wind", name: "Wind speed", value: (weather.wind.speed.roundDouble() + "m/s"))
Spacer()
WeatherRow(logo: "humidity", name: "Humidity", value: (weather.main.humidity.roundDouble() + "%"))
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.padding(.bottom, 20)
.foregroundColor(Color(hue: 0.685, saturation: 0.988, brightness: 0.377))
.background(.white)
.cornerRadius(20, corners: [.topLeft, .topRight])
}
}
.edgesIgnoringSafeArea(.bottom)
.background(Color(hue: 0.685, saturation: 0.988, brightness: 0.377))
.preferredColorScheme(.dark)
}
}
struct WeatherView_Previews: PreviewProvider {
static var previews: some View {
WeatherView(weather: previewWeather) < -Error is here
}
}
import Foundation
// previewWeather var here -> var previewWeather: ResponseBody = load("weatherData.json").
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
Try this to fix your error:
struct Weather_Previews: PreviewProvider {
static var previewWeather = previewWeather() // <- your "Model Data"
static var previews: some View {
WeatherView(weather: previewWeather)
}
}

SwiftUI pass parameter to view static function

I have the following code I have to make sure to pass an error message, if nothing is passed then take a generic message as error.
The problem is that I have to pass it to a static function and then set the error but I'm not succeeding.
Can you give me a hand?
ErrorView.showWindow(error: "La cartella già esiste.") error <-- "La cartella già esiste."
ErrorView.showWindow() error <-- "C'è stato un problema."
import SwiftUI
struct ErrorView: View {
var error: String = "C'è stato un problema."
var body: some View {
HStack(alignment: .center) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 96, height: 96)
.shadow(radius: 5)
.padding(16)
.padding(.horizontal, 6)
.padding(.bottom, 22)
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading) {
Text("Git Repository")
.font(Font.title.weight(.thin))
}
.padding(.bottom, 8)
VStack(alignment: .leading, spacing: 14) {
Text(error)
}
.font(.system(size: 11))
.frame(maxHeight: .infinity)
}
}
.padding(.top, 10)
.padding(.bottom, 24)
.padding(.horizontal, 16)
.frame(width: 525, height: 200)
}
static func showWindow(error:String) {
let viewController = NSHostingController(rootView: ErrorView())
let windowController = NSWindowController(window: NSWindow(contentViewController: viewController))
if let window = windowController.window {
window.title = "Title"
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
window.animationBehavior = .alertPanel
window.styleMask = [.titled, .closable]
}
windowController.showWindow(nil)
NSApp.activate(ignoringOtherApps: true)
}
}
struct ErrorView_Previews: PreviewProvider {
static var previews: some View {
ErrorView()
}
}
Set default error message to showWindow function instead of ErrorView error var.
Another mistake is you are not passing an error message to your ErrorView.
So final code is
struct ErrorView: View {
var error: String //<< Here
var body: some View {
HStack(alignment: .center) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 96, height: 96)
.shadow(radius: 5)
.padding(16)
.padding(.horizontal, 6)
.padding(.bottom, 22)
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading) {
Text("Git Repository")
.font(Font.title.weight(.thin))
}
.padding(.bottom, 8)
VStack(alignment: .leading, spacing: 14) {
Text(error)
}
.font(.system(size: 11))
.frame(maxHeight: .infinity)
}
}
.padding(.top, 10)
.padding(.bottom, 24)
.padding(.horizontal, 16)
.frame(width: 525, height: 200)
}
static func showWindow(error:String = "C'è stato un problema.") { //<< Here
let viewController = NSHostingController(rootView: ErrorView(error: error)) //<< Here
let windowController = NSWindowController(window: NSWindow(contentViewController: viewController))
if let window = windowController.window {
window.title = "Title"
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
window.animationBehavior = .alertPanel
window.styleMask = [.titled, .closable]
}
windowController.showWindow(nil)
NSApp.activate(ignoringOtherApps: true)
}
}

Asynchronous Image Loading Bug

I am hitting a strange bug when it comes to Asynchronous Image Loading, where when I enter a view, the shows up like it is suppose to do, then for some reason the image drops and all I see is the "Loading..." placeholder. I used this tutorial when building my loader and the following script is my Article View. I have a Global Functions file, which includes reference to Combine and Foundation for my various functions through out the app. I am just not fully understanding why the image is showing for a brief moment, then calls the placeholder. Thanks!
import SwiftUI
struct ArticleView: View {
#Environment(\.imageCache) var cache: ImageCache
var articleID: Int
#ObservedObject private var data = Result2()
let defaults = UserDefaults.standard
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init(articleID: Int){
self.articleID = articleID
self.data.results.append(Story.init(id: 0, title: "test", image: "", story: "", published: "", author: ""))
self.loadArticle(CDNLink: "http://\(self.defaults.object(forKey: "domain") as! String)/cdn?funct=fetchArticle&articleID=\(self.articleID)")
}
var backBtn: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label:{
Image(systemName: "lessthan.circle")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(Color.primaryRed)
.padding(.top, 6)
.padding(.leading, 10)
})
}
var body: some View {
VStack(alignment: .leading){
ScrollView {
ForEach(data.results, id: \.id) { result in
Group{
AsyncImage(
url: URL(string: result.image)!,
placeholder: Text("Loading..."), configuration: { $0.resizable() })
.frame(height: 250)
.aspectRatio(contentMode: .fill)
VStack(alignment: .leading) {
Text("\(result.title)")
.font(.system(size: 18))
.fontWeight(.heavy)
.foregroundColor(.primary)
.multilineTextAlignment(.leading)
HStack {
Text("\(result.author)")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
Text("|")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
Text("\(result.published)")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
}.frame(width: UIScreen.main.bounds.size.width - 40, alignment: .leading)
}.frame(width: UIScreen.main.bounds.size.width - 40)
}
Text("\(result.story)")
}
}
}
.frame(maxWidth: .infinity)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backBtn)
}
func loadArticle(CDNLink: String) {
guard let url = URL(string: CDNLink) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
return
} else {
do {
let decodedResponse = try JSONDecoder().decode(Response2.self, from: data!)
print(decodedResponse)
DispatchQueue.main.async {
self.data.results.append(contentsOf: decodedResponse.results)
self.data.results.remove(at: 0)
}
} catch let err {
print("Error parsing: \(err)")
}
}
}.resume()
}
}
struct Response2: Codable {
var results: [Story]
}
struct Story: Codable, Identifiable {
var id: Int
var title: String
var image: String
var story: String
var published: String
var author: String
}
struct ArticleView_Previews: PreviewProvider {
static var previews: some View {
ArticleView(articleID: 0)
}
}
Edit: I have made the changes suggested and I am still seeing the error, you can see the error live by checking out this link.
Hi most likely it is due to the fact that you are using #ObservedObject. This object is discarded and initialized every time your view state changes and is triggering a view re-render. Try using #StateObject instead of #ObservedObject. The #StateObject will be initialized only once in the view.
A #StateObject can be also used with an ObservedObject model. It is kind of a combination of #ObservedObject and #State.
Okay so I created a new solution to patch this issue up and I am very glad that this little work around is working. So, I started looking at what I could do server side to optimize content being pulled into the app. What I did was use PHP to encode the image with base64, then the app pulls it in and decodes the base64 data and the image populates with lightning speed!
Server Side code:
$article['image'] = base64_encode(file_get_contents(PATH . "/cache/content/topstory/" . $row['app_article']));
Client Side code:
let dataDecoded:NSData = NSData(base64Encoded: result.image, options: NSData.Base64DecodingOptions(rawValue: 0))!
let decodedimage:UIImage = UIImage(data: dataDecoded as Data)!
Image(uiImage: decodedimage)
.frame(height: 250)
.aspectRatio(contentMode: .fill)