I have a small problem. Im making a post request to log in, the "authenticated" is set to "true" when the request is succesful but the view is not changing.
When i'm pressing the Log In button, i'm calling this function :
#Published var authenticatedAngajator = false
func postAuthAngajator(email: String, password: String) {
guard let url = URL(string: postUrl) else { return }
let body: [String: Any] = ["email": email, "password": password, "acctype" : "angajator"]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.percentEncoded()
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
let resData = try! JSONDecoder().decode(ServerMessage.self, from: data)
print(resData.result)
if resData.result == "success" {
DispatchQueue.main.async {
self.authenticatedAngajator = true
}
}
}.resume()
}
And this is my content view, but when the request is called, the view is not changing and i do not know why. This is my content view :
struct ContentView: View {
#EnvironmentObject var userAuth : HttpAuth
var body: some View {
if userAuth.authenticatedNevazator {
MainViewNevazator()
}
if userAuth.authenticatedAngajator {
MainViewAngajator()
}
if userAuth.authenticatedUniversitate {
MainViewUniversitate()
}
else
{
MainView()
}
}
}
This is where postAuthAngajator is called.
struct LoginViewNevazator: View {
#State var emailNevazator: String = ""
#State var passwordNevazator: String = ""
#ObservedObject var manager = HttpAuth()
var body: some View {
ZStack {
VStack {
UserImage()
UsernameTextField(username: $emailNevazator)
PasswordSecureField(password: $passwordNevazator)
Button(action: {
manager.postAuthNevazator(email: self.emailNevazator, password: self.passwordNevazator)
}) {
LoginButtonContent()
}
What can i do in this situation ?
Related
I'm using GitHub api to
get issues
update issue title or description
I call self.get() inside update() method, to get the updated list of issues after the successful update.
But after update when I get back to screen with list of issues, its title/description shows the previous values.
Could you please say how can I fix it? Thanks!
Here is the model:
struct Issue: Codable, Hashable {
let id: Int
let number: Int
var title: String
var body: String? = ""
}
ViewModel:
class ViewModel: ObservableObject {
#Published var issues: [Issue] = []
private let token: String = "_token_"
init() {
get()
}
func get() {
guard let url = URL(string: "https://api.github.com/repos/\(repoOwner)/\(repo)/issues") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/vnd.github+json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard let data = data, error == nil else {
return
}
do {
let response = try JSONDecoder().decode([Issue].self, from: data)
DispatchQueue.main.async {
self?.issues = response
}
} catch {
print("error during get issues \(error.localizedDescription)")
}
}.resume()
}
func update(number: Int, title: String, body: String) {
guard let url = URL(string: "https://api.github.com/repos/\(repoOwner)/\(repo)/issues/\(number)") else {
return
}
let json: [String: Any] = ["title": "\(title)",
"body": "\(body)"]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var patchRequest = URLRequest(url: url)
patchRequest.httpMethod = "PATCH"
patchRequest.addValue("application/vnd.github+json", forHTTPHeaderField: "Accept")
patchRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
patchRequest.httpBody = jsonData
URLSession.shared.dataTask(with: patchRequest) { data, response, error in
guard let data = data, error == nil else {
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
self.get()
}.resume()
}
}
Views
List of issues:
struct HomeView: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.issues, id: \.id) { issue in
NavigationLink(destination: EditView(issue: issue)) {
IssueRow(issue: issue)
}
}
}
.navigationTitle("Github issues")
}
}
}
struct IssueRow: View {
var issue: Issue
var body: some View {
VStack {
HStack {
Text("#\(issue.number)")
.bold()
Text("\(issue.title)")
.bold()
.lineLimit(1)
Spacer()
}.foregroundColor(.black)
HStack {
Text(issue.body ?? "no description")
.foregroundColor(.gray)
.lineLimit(2)
Spacer()
}
Spacer()
}
}
}
Edit view:
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var viewModel: ViewModel
var issue: Issue
#State var title: String = ""
#State var description: String = ""
func save() {
viewModel.update(number: issue.number, title: title, body: description)
dismiss()
}
func dismiss() {
presentationMode.wrappedValue.dismiss()
}
var body: some View {
Content(issue: issue, title: $title, description: $description, save: save)
}
}
extension EditView {
struct Content: View {
var issue: Issue
#Binding var title: String
#Binding var description: String
let save: () -> Void
var body: some View {
VStack {
List {
Section(header: Text("Title")) {
TextEditor(text: $title)
}
Section(header: Text("Description")) {
TextEditor(text: $description)
.multilineTextAlignment(.leading)
}
}
saveButton
}
.onAppear(perform: setTitleAndDescription)
.navigationTitle("Update issue")
}
var saveButton: some View {
Button(action: save) {
Text("Save changes")
}
}
func setTitleAndDescription() {
self.title = issue.title
self.description = issue.body ?? ""
}
}
}
I've been trying to get a list of entities from a graphql endpoint but I can't figure it out. Also, the console in my Xcode v13.4 isn't showing anything even though I have some print() statements in the code, so that's not helping - I've found where it is at the bottom of the window but it's always blank.
My View to get the data is below, the DetailView is the link following a link from the main ContentView. the loadData function content, I got the code from Postman after testing the graphql call.
import SwiftUI
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
//array of properties
struct Response: Codable {
var data: Properties
}
struct Properties: Codable {
var properties: Nodes
}
struct Nodes: Codable {
var nodes: [Result]
}
struct Result: Codable {
var propertyId: Int
var title: String
}
struct DetailView: View {
#State private var results = [Result]()
var body: some View {
List(results, id: \.propertyId) { property in
VStack(alignment: .leading) {
Text(property.title)
.font(.headline)
}
}
.task{
await loadData()
}
}
func loadData() async {
let semaphore = DispatchSemaphore (value: 0)
let parameters = "{\"query\":\"{\\n properties {\\n nodes {\\n title(format: RENDERED)\\n propertyId\\n }\\n }\\n}\",\"variables\":{}}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "http://DOMAIN/graphql")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
do {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.data.properties.nodes
}
semaphore.signal()
}
task.resume()
semaphore.wait()
} catch {
print("Invalid data")
}
}
}
The output of the graphql call is
{
"data": {
"properties": {
"nodes": [
{
"title": "MY TITLE",
"propertyId": 00001
}
]
}
}
}
EDIT
Swapped try? for try! as suggested
if let decodedResponse = try! JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.data.properties.nodes
}
error: Initializer for conditional binding must have Optional type, not 'Response'
I managed to get it all working with the below...
import SwiftUI
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
//array of properties
struct Response: Codable {
var data: Properties
}
struct Properties: Codable {
var properties: Nodes
}
struct Nodes: Codable {
var nodes: [Result]
}
struct Result: Codable {
var propertyId: Int
var title: String
}
struct DetailView: View {
#State private var results = [Result]()
#State private var test = "one"
var body: some View {
List(results, id: \.propertyId) { property in
VStack(alignment: .leading) {
Text(property.title)
.font(.headline)
}
}.task{
await loadData()
}
}
func loadData() async {
let semaphore = DispatchSemaphore (value: 0)
let parameters = "{\"query\":\"{\\n properties {\\n nodes {\\n title(format: RENDERED)\\n propertyId\\n }\\n }\\n}\",\"variables\":{}}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://DOMAIN/graphql")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
do {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
let decodedResponse = try! JSONDecoder().decode(Response.self, from: data)
results = decodedResponse.data.properties.nodes
semaphore.signal()
}
task.resume()
semaphore.wait()
} catch {
print("Invalid data \(error)")
}
}
}
Much to learn :)
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
First of all, this is my first attempt at Swift so I'm not really sure what I'm doing. I'm learning as I go right now and have hit a roadblock.
I'm trying to implement a WatchOS app that will call an API on a set timer to track fluctuations in some crypto prices.
I have figured out how to make the API call and get the JSON parsed to a point where I can print the data but I'm struggling to get it out of the closure and to my interface. I know the proper way to do this is with a completion handler but I can't seem to get a solid understanding of how to make that work in this scenario.
Any help would be appreciated
import SwiftUI
var refresh = bitcoin()
var btc: String = refresh
var eth: String = "ETH"
var doge: String = "DOGE"
struct ContentView: View {
var body: some View {
VStack(alignment: .leading ){
Label("\(btc)", image: "eth").padding(.vertical, 10.0)
Label("\(eth)", image: "eth").padding(.vertical, 10.0)
Label("\(doge)", image: "doge").padding(.vertical, 10.0)
}
.scaledToFill()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
struct responseData: Codable{
let data: Response?
}
struct Response: Codable{
var id: String
var rank: String
var symbol: String
var name: String
var supply: String
var maxSupply: String
var marketCapUsd: String
var volumeUsd24Hr: String
var priceUsd: String
var changePercent24Hr: String
var vwap24Hr: String
}
func bitcoin() -> String{
var result: String = "btc"
var request = URLRequest(url: URL(string: "https://api.coincap.io/v2/assets/bitcoin")!,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
let response = try! JSONDecoder().decode(responseData.self, from: data)
result = (response.data?.priceUsd)!
print(result)
}
task.resume()
return result
}
There many ways to achieve what you want, one way is to use "ObservableObject". Try something like this:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CoinModel: ObservableObject {
#Published var btcPriceUsd = "not yet available"
#Published var ethPriceUsd = "not yet available"
#Published var dogePriceUsd = "not yet available"
}
struct ContentView: View {
#StateObject var coins = CoinModel()
var body: some View {
VStack(alignment: .leading ){
Label("\(coins.btcPriceUsd)", image: "btc").padding(.vertical, 10.0)
Label("\(coins.ethPriceUsd)", image: "eth").padding(.vertical, 10.0)
Label("\(coins.dogePriceUsd)", image: "doge").padding(.vertical, 10.0)
}
.scaledToFill()
.onAppear {
// bitcoin()
bitcoin2 { price in
coins.btcPriceUsd = price
}
}
}
func bitcoin() {
var request = URLRequest(url: URL(string: "https://api.coincap.io/v2/assets/bitcoin")!,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
return
}
let response = try! JSONDecoder().decode(responseData.self, from: data)
if let respData = response.data {
DispatchQueue.main.async {
coins.btcPriceUsd = respData.priceUsd
}
}
}
task.resume()
}
}
EDIT: if you really want to use completion, then try this:
func bitcoin2(completion: #escaping (String) -> Void) {
var request = URLRequest(url: URL(string: "https://api.coincap.io/v2/assets/bitcoin")!,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
return completion("")
}
let response = try! JSONDecoder().decode(responseData.self, from: data)
if let respData = response.data {
DispatchQueue.main.async {
completion(respData.priceUsd)
}
}
}
task.resume()
}
I was making a search bar with onchange in which it recieve some data for every change. But sometime when I type fast the working is not proper. I think I need to use debounce here. I've tried and fails (beginner to swift)
Here is my code :
struct Result: Codable{
var searchResult :[Real]
}
struct Real: Codable{
var _id : String
var name : String
}
struct ContentView: View { #State private var text: String = ""
#State private var isEditing = false
#State private var results = [Result]()
#State private var real = [Real]()
var body: some View {
VStack(alignment: .leading){
HStack {
TextField("Search ...", text: $text)
.onChange(of: text) {
guard let url = URL(string: "https://'api link'")else{
print("inavlid url")
return
}
let json: [String: Any] = ["searchKey":text]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = jsonData
URLSession.shared.dataTask(with: request){
data,response,error in
if let data = data{
do {
let decodedResponse = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.real = decodedResponse.searchResult
}
print("tommtoow")
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
return
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
print($0)
}
}
}
}
}
}
}
Try to implement a general API request with a method .onAppear(perform: ) and onChange can be used after to filter results
with "onChange" you do your request every time you type something, hence your issue. What you probably want is to use "onSubmit" (ios15) or "onCommit" (will be deprecated), that way you do your request only when you are finished typing it in.
On the click of a button I am trying to download a new random image and update the view. When the app loads it displays the downloaded image. When the button is clicked the image seems to download but the view is never updated and displays the place holder image. Am I missing something here, any ideas? Here is a simplified version.
import SwiftUI
struct ContentView : View {
#State var url = "https://robohash.org/random.png"
var body: some View {
VStack {
Button(action: {
self.url = "https://robohash.org/\(Int.random(in:0 ..< 10)).png"
}) {
Text("Get Random Robot Image")
}
URLImage(url: url)
}
}
}
class ImageLoader: BindableObject {
var downloadedImage: UIImage?
let didChange = PassthroughSubject<ImageLoader?, Never>()
func load(url: String) {
guard let imageUrl = URL(string: url) else {
fatalError("Image URL is not correct")
}
URLSession.shared.dataTask(with: imageUrl) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
self.didChange.send(nil)
}
return
}
self.downloadedImage = UIImage(data: data)
DispatchQueue.main.async {
print("downloaded image")
self.didChange.send(self)
}
}.resume()
}
}
import SwiftUI
struct URLImage : View {
#ObjectBinding private var imageLoader = ImageLoader()
var placeholder: Image
init(url: String, placeholder: Image = Image(systemName: "photo")) {
self.placeholder = placeholder
self.imageLoader.load(url: url)
}
var body: some View {
if let uiImage = self.imageLoader.downloadedImage {
print("return downloaded image")
return Image(uiImage: uiImage)
} else {
return placeholder
}
}
}
The problem seems to be related to some kind of lost synchronization between the ContentView and the ImageURL (that happens after the button click event).
A possible workaround is making the ImageURL a #State property of the ContentView.
After that, inside the scope of the button click event, we can call the image.imageLoader.load(url: ) method. As the download of the image ends, the publisher (didChange) will notify the ImageURL and then the change is correctly propagated to the ContentView.
import SwiftUI
import Combine
enum ImageURLError: Error {
case dataIsNotAnImage
}
class ImageLoader: BindableObject {
/*
init(url: URL) {
self.url = url
}
private let url: URL */
let id: String = UUID().uuidString
var didChange = PassthroughSubject<Void, Never>()
var image: UIImage? {
didSet {
DispatchQueue.main.async {
self.didChange.send()
}
}
}
func load(url: URL) {
print(#function)
self.image = nil
URLSession.shared.dataTask(with: url) { (data, res, error) in
guard error == nil else {
return
}
guard
let data = data,
let image = UIImage(data: data)
else {
return
}
self.image = image
}.resume()
}
}
URLImage view:
struct URLImage : View {
init() {
self.placeholder = Image(systemName: "photo")
self.imageLoader = ImageLoader()
}
#ObjectBinding var imageLoader: ImageLoader
var placeholder: Image
var body: some View {
imageLoader.image == nil ?
placeholder : Image(uiImage: imageLoader.image!)
}
}
ContentView:
struct ContentView : View {
#State var url: String = "https://robohash.org/random.png"
#State var image: URLImage = URLImage()
var body: some View {
VStack {
Button(action: {
self.url = "https://robohash.org/\(Int.random(in: 0 ..< 10)).png"
self.image.imageLoader.load(url: URL(string: self.url)!)
}) {
Text("Get Random Robot Image")
}
image
}
}
}
Anyway I will try to investigate the problem and if I will know something new I will modify my answer.