I need to post a http request to login view (SwiftUI), my code follow
I have in HttpAuth.swift:
import Foundation
import Combine
struct ServerMessage: Decodable {
let res, message: String
}
class HttpAuth: ObservableObject {
let objectWillChange = PassthroughSubject<HttpAuth, Never>()
var authenticated = false {
didSet {
objectWillChange.send(self)
}
}
func postAuth(username: String, password: String) {
guard let url = URL(string: "http://mysite/loginswift.php") else { return }
let body: [String: String] = ["username": username, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", 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.res)
if resData.res == "correct" {
DispatchQueue.main.async {
self.authenticated = true
}
}
}.resume()
}
}
And in ContentView.swift:
import SwiftUI
struct ContentView: View {
#State private var username: String = ""
#State private var password: String = ""
#State var manager = HttpAuth()
var body: some View {
VStack(alignment: .leading) {
if self.manager.authenticated {
Text("Correct!").font(.headline)
}
Text("Username")
TextField("placeholder", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
.autocapitalization(.none)
Text("Password")
SecureField("placeholder", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
Button(action: {
self.manager.postAuth(username: self.username, password: self.password)
}) {
HStack{
Spacer()
Text("Login")
Spacer()
}
.accentColor(Color.white)
.padding(.vertical, 10)
.background(Color.red)
.cornerRadius(5)
.padding(.horizontal, 40)
}
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It works, i receive the answer from server but it doesn't update the ContentView, the 'self.manager.authenticated' in ContentView doesn't refresh from HttpAuth class.
This part of code doesn't work:
if self.manager.authenticated {
Text("Correct!").font(.headline)
}
The 'authenticated' still on false.
How can i fix it, thank you.
Although the accepted answer meets the objective I don't think it's the best way to do it because it implements #EnvironmentObject and it should be used for global application issues and not for the specific request of a view.
You can implement a "ViewModel" that is specific for the view, to do the work of the request and make the update of the variable.
Documentation: https://developer.apple.com/documentation/combine/observableobject
The implementation for your code would be like this, based on the changes suggested by #Chris:
This works in Xcode Version 11.2 (11B52)
HttpAuth.swif:
import Foundation
import SwiftUI
import Combine
class HttpAuth: ObservableObject {
#Published var authenticated = false
func postAuth(username: String, password: String) {
guard let url = URL(string: "http://mysite/loginswift.php") else { return }
let body: [String: String] = ["username": username, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", 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.res)
if resData.res == "correct" {
DispatchQueue.main.async {
self.authenticated = true
}
}
}.resume()
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var username: String = ""
#State private var password: String = ""
#ObservedObject var manager = HttpAuth()
var body: some View {
VStack(alignment: .leading) {
if self.manager.authenticated {
Text("Correct!").font(.headline)
}
Spacer()
Text("Username")
TextField("placeholder", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
.autocapitalization(.none)
Text("Password")
SecureField("placeholder", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
Button(action: {
self.manager.postAuth(username: self.username, password: self.password)
}) {
HStack{
Spacer()
Text("Login")
Spacer()
}
.accentColor(Color.white)
.padding(.vertical, 10)
.background(Color.red)
.cornerRadius(5)
.padding(.horizontal, 40)
}
Spacer()
}.padding()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
you can try this:
instead of
let objectWillChange = PassthroughSubject<HttpAuth, Never>()
var authenticated = false {
didSet {
objectWillChange.send(self)
}
}
use
#published var authenticated = false
and instead of
#State var manager = HttpAuth()
use
#EnvironmentObject private var manager: HttpAuth
and of course do this when calling ContentView:
ContentView().environmentObject(manager)
and somewhere outside class as global variable do
var manager = HttpAuth()
then it should work.
Related
I know I can use for each for this, but every time I try to implement according to documentation it throws some kind of error regarding syntax.
Here is my view:
import SwiftUI
import Combine
struct HomeTab: View {
#StateObject var callDevices = CallDevices()
var body: some View {
NavigationView {
devices
.onAppear {
callDevices.getDevices()
}
}
}
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(content: callDevices.getDevices(), id: \.self) { device in
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
}
struct HomeTab_Previews: PreviewProvider {
static var previews: some View {
HomeTab()
}
}
Here is my Call Devices which works without issue in other views:
class CallDevices: ObservableObject {
private var project_id: String = "r32fddsf"
#Published var devices = [Device]()
func getDevices() {
guard let url = URL(string: "www.example.com") else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Authorization")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {print(error!.localizedDescription); return }
// guard let data = data else {print("empty data"); return }
let theData = try! JSONDecoder().decode(Welcome.self, from: data!)
DispatchQueue.main.async {
self.devices = theData.devices
}
}
.resume()
}
}
is the issue in the way I am calling my function?
try this:
(you may need to make Device Hashable)
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(callDevices.devices, id: \.self) { device in // <-- here
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
If Device is Identifiable, you can remove the id: \.self.
struct Device: Identifiable, Hashable, Codable {
let id = UUID()
var Name = ""
var Status = ""
// ... any other stuff you have
}
I am stuck at the simplest place right now.. I'm making a network request and just want the view to be updated once the JSON is received..
And I verified that:
JSON is valid
Valid response received (verified in print statement)
I've done this about 50 times and don't know why I'm stuck at this point.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
HStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await NetworkManager().getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
private let apiKey = ""//removed
private var baseUrlString: String {
"https://newsapi.org/v2/"
}
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: baseUrlString + "everything?q=\(searchItem)&apiKey=\(apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
DispatchQueue.main.async {
self.newsItem = news
self.articles = self.newsItem!.articles
}
}
}
struct News: Codable {
var totalResults: Int?
let articles: [Article]
}
struct Article: Codable, Hashable {
let author: String?
let title: String
let description: String
let url: String
let urlToImage: String?
let publishedAt: String
let content: String
}
Edit: removed apiKey
getAllArticles() is view-related, which means you should probably implement this function inside the View instead of ObservableObject.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension ContentView {
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: nm.baseUrlString + "everything?q=\(searchItem)&apiKey=\(NetworkManager.apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
// Not necessary
// DispatchQueue.main.async {
nm.newsItem = news
nm.articles = nm.newsItem!.articles
// }
}
}
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
static let apiKey = "YOUR_API_KEY"
var baseUrlString: String {
"https://newsapi.org/v2/"
}
}
I'm struggling in a big way to get some basic code working that allows me to change a view in a Swiftui project.
I have 3 views: my default ContentView, a login screen and a main menu.
The project loads to ContentView which is just a logo. I have a boolean value which defaults to false and an extension function which either loads the login screen, or main menu dependent on the value of that boolean.
This part is working fine, project loads and i see the login page. The login button calls a function which does a URLsession, and depending on the returned value from that, sets the boolean flag to true or leaves it as false in the case of a failed login.
The bit im struggling with is getting the function to change the view. I can toggle the boolean flag in the function fine, but if I include a statement such as MainMenu() to load my main menu view, nothing happens.
I have experimented with observable objects and "subscribers" to try to get this working but i'm not sure if this is actually needed and I had no joy getting it working.
any help is greatly appreciated
Full code:
import SwiftUI
var isLoggedin = false
var authenticationFailure = false
func DoLogin(username: inout String, password: inout String){
print(isLoggedin)
let url = URL(string: "https://www.example.com/mobile/ios/test.php")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: Any] = [
"username": username,
"password": password]
request.httpBody = parameters.percentEncoded()
let task = URLSession.shared.dataTask(with: request) { data,response,error in
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else{
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else {
print("statuscode should be 2xx, got \(response.statusCode)")
print("response = \(response)")
return
}
let responseString = String(data: data, encoding: .utf8)
if responseString == "1"{
print("Logged in")
isLoggedin = true
print(isLoggedin)
MainMenu()
}
else{
print("NO LOGIN")
isLoggedin = false
}
}
task.resume()
}
extension View {
#ViewBuilder func changeView(_ isLoggedin: Bool) -> some View {
switch isLoggedin {
case false: LoginView()
case true: MainMenu()
}
}
}
struct ContentView: View {
#State var isLoggedin = false
var body: some View {
Color.clear
.changeView(isLoggedin)
VStack{
Image("logo")
.padding(.bottom, 40)
}
}
}
struct LoginView: View {
#State var username: String = ""
#State var password: String = ""
#State var isLoggedin = false
var body: some View {
VStack{
Form{
TextField("Username: ", text:$username)
.frame(maxWidth: .infinity, alignment: .center)
.autocapitalization(.none)
SecureField("Password: ",text:$password)
Button("Login"){
DoLogin(username: &username, password: &password)
}
}
.padding(.top, 100)
}
}
}
struct MainMenu: View{
#State var isLoggedin = true
var body: some View{
VStack{
Text("Main Menu")
}
}
}
/*struct ContentView_Previews: PreviewProvider {
static var previews: some View {
/*ContentView() */
}
}*/
You have some problems with your code.
In your Content view
#State var isLoggedin = false
isn't being changed by anything inside the body of the struct, so it is always going to be false.
Your LoginView calls doLogin but it doesn't change any variables that the views use to render themselves. In the body of your doLogin method it is returning views, but it isn't returning them to anything.
Here is an example that does sort of what you want. shows different screens depending on state. SwiftUI shows views depending on states, so you need to change states to show different views. I've done this in one file so it's easier to show here:
import SwiftUI
class ContentViewModel: ObservableObject {
enum ViewState {
case initial
case loading
case login
case menu
}
#Published var username = ""
#Published var password = ""
#Published var viewState = ViewState.initial
var loginButtonDisabled: Bool {
username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ||
password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
func goToLogin() {
viewState = .login
}
func login() {
viewState = .loading
// I'm not actually logging in, just randomly simulating either a successful or unsuccessful login after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if Bool.random() {
self.viewState = .menu
} else {
self.viewState = .login
}
}
}
}
struct ContentView: View {
#StateObject var viewModel = ContentViewModel()
var body: some View {
ZStack {
initialView
loginView
loadingView
menuView
}
}
private var initialView: InitialView? {
guard .initial == viewModel.viewState else { return nil }
return InitialView(viewModel: viewModel)
}
private var loginView: LoginView? {
guard .login == viewModel.viewState else { return nil }
return LoginView(viewModel: viewModel)
}
private var loadingView: LoadingView? {
guard .loading == viewModel.viewState else { return nil }
return LoadingView()
}
private var menuView: MenuView? {
guard .menu == viewModel.viewState else { return nil }
return MenuView()
}
}
struct InitialView: View {
#ObservedObject var viewModel: ContentViewModel
var body: some View {
VStack {
Text("Initial View")
.font(.largeTitle)
.padding()
Button("Login") { viewModel.goToLogin() }
}
}
}
struct LoginView: View {
#ObservedObject var viewModel: ContentViewModel
var body: some View {
VStack {
Text("Login View")
.font(.largeTitle)
.padding()
TextField("Username", text: $viewModel.username)
.padding()
TextField("Password", text: $viewModel.password)
.padding()
Button("Login") {viewModel.login() }
.padding()
.disabled(viewModel.loginButtonDisabled)
}
}
}
struct LoadingView: View {
var body: some View {
Text("Loading View")
.font(.largeTitle)
}
}
struct MenuView: View {
var body: some View {
Text("Menu View")
.font(.largeTitle)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is all driven off one view model which publishes an enum state that is used by ContentView to show the different views. This is possible because in groups (such as the ZStack), a nil view is not rendered.
You can clone a project with this from https://github.com/Abizern/SO-68407322
I have a code that makes a http Request, gets an array with filenames from that, displays them each with an image and the filename below. Everything works fine.
Now I made each image a button that opens a detail page.
That works but at the top it should say the matching filename from the page before.
But I am not able to hand over the filename (name) from ContentView4 to the next page (ts).
The language is SwiftUi
Could you please help me?
Thanks
Nikias
Here is my code:
import SwiftUI
struct ContentView4: View {
#State var showingDetail = false
#State var username: String = "."
#State var password: String = "."
#State private var name = String("Nikias2")
#State private var t = String()
#State private var x = -1
#State var dateien = ["word.png"]
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
$showingDetail) {
ts(name: t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
doHttpRequest()
}
}
}
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
ContentView4()
}
}
struct ts: View {
#State var hin = false
#State var um = false
#State var datname: String = ""
var name: String
var body: some View {
NavigationView {
VStack {
Text(name)
.font(.system(size: 60))
.foregroundColor(.black)
.padding(50)
Button(action: {
self.hin.toggle()
}) {
Text("+")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.yellow)
.cornerRadius(35.0)
}
.padding()
if hin {
HStack {
Text("Datei auswählen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei hochladen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei herunterladen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.blue)
.cornerRadius(35.0)
Button(action: {
self.um.toggle()
}) {
Text("Datei umbenennen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.green)
.cornerRadius(35.0)
}
.padding()
if um {
HStack {
TextField(name, text: $datname)
.font(.headline)
.frame(width: 150, height: 70)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei umbenennen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.green)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei löschen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.red)
.cornerRadius(35.0)
}
}
}
}
I believe your issue is a result of using #State variables to store all of the attributes. #State variables are not consistent and get refreshed in the background by SwiftUI depending on your views visibility.
The piece that you are missing is a view controller class stored in an #EnviornmentObject variable. This class gets Initiated in your main contentView and is used to keep track and alter of all your attributes.
Each ContentView should reference the single #EnviornmentObject and pull data from that class.
Another solution which may work would be to replace all your #State variables with #StateObject vars. #StateObject vars are basically #State vars but get initiated before the struct get loaded and the value is kept consistent regardless of the view state of the parent struct.
Here is a rough implementation of #EnvironmentObject within your project.
Basically use the #EnvironmentObject to pass values to child views
ContentView4.swift
struct ContentView4: View {
#EnvironmentObject cv4Controller: ContentView4Controller
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< cv4Controller.dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = cv4Controller.dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(cv4Controller.t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
cv4Controller.$showingDetail) {
ts(name: cv4Controller.t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
cv4Controller.doHttpRequest()
}
}
}
ContentView4Controller.swift
class ContentView4Controller: ObservableObject {
#Published var showingDetail = false
#Published var username: String = "."
#Published var password: String = "."
#Published private var name = String("Nikias2")
#Published private var t = String()
#Published private var x = -1
#Published private var t = String()
#Published private var x = -1
#Published var dateien = ["word.png"]
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
Example of main ContentView.swift
struct ContentView: View {
var cv4Controller: ContentView4Controller = ContentView4Controller()
var body: some view {
// your main page output
GeometryReader { geo in
// just a guess for what you have in your main contentView
switch(page) {
case .main:
ContentView2()
default:
ContentView4()
break
}
}.environmentObject(cv4Controller) // this will make cv4Controller available to all child view structs
}
}
Add #Binding wrapper to the "name" variable in your ts view. And pass the t variable as a binding by adding a "$". This will keep your ts name variable updated to whatever is value it has in the parent view.
Also why do you use a NavigationView in your ts View?
struct ContentView4: View {
...
#State private var t = String()
...
var body: some View {
...
ZStack{
...
}
.sheet(isPresented: $showingDetail) {
ts(name: $t)
}
...
}
func doHttpRequest() {
...
}
}
struct ts: View {
...
#Binding var name: String
var body: some View {
...
}
}
My starting code works, but It's just displaying the Filenames in a row and if I tap a random image, the name won't fit, only if I'm going down in the row and tap them. The problem is, that I don't know how to set the variable to the id, not to pass them to the next view. Has anyone got and idea how I can pass the right filename into a variable in the for loop and read it in the next view?
Facing problem to understand how to move Object details to another view using NavigationLink, I have read many articles and watched video, they all do the same as I do except for the Preview struct, they use local data and easily they map the view to the first item of the data like data[0]. While in my case, I fetch the data online, hence the above way did not help me to overcome the issue with the Preview struct, ERROR: Missing argument for parameter
Articles have been read:
developer.apple.com/tutorials/swiftui/building-lists-and-navigation
www.raywenderlich.com/5824937-swiftui-tutorial-navigation
www.youtube.com/watch?v=BCSP_uh0co0&ab_channel=azamsharp
/// Main View Code:
import SwiftUI
import SDWebImageSwiftUI
struct HomeView: View {
#State var posts: [Posts] = []
#State var intPageNo: Int = 0
var body: some View {
NavigationView {
List(posts) {post in
NavigationLink(destination: ViewPostView(post: post)){
VStack{
HStack{
WebImage(url: URL(string: post.featured_image))
.resizable()
.placeholder(Image("logo"))
.frame(width: 150, height: 150, alignment: .center)
VStack(alignment: .leading, spacing: 10.0){
Text("By: \(post.author_name)")
Text("Since: \(post.since)")
Text("City: \(post.city_name)")
Text("Category: \(post.category_name)")
}
.font(.footnote)
Spacer()
}
Text(post.title)
.font(.body)
.fontWeight(.bold)
.frame(alignment: .trailing)
.flipsForRightToLeftLayoutDirection(true)
}
}
}
.onAppear{
self.intPageNo += 1
ApiPosts().getPosts(intPage: self.intPageNo){(posts) in
self.posts = posts
}
}
.navigationBarTitle(Text("Home"))
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
/// Detail View Code:
import SwiftUI
struct ViewPostView: View {
#State var comments: [Comments] = []
#State var post: Posts
var body: some View {
NavigationView {
VStack{
Text(post.post_content)
.frame(alignment: .trailing)
List(comments){comment in
VStack(alignment: .trailing, spacing: 10){
HStack(spacing: 40){
Text(comment.author_name)
Text(comment.comment_date)
}
Text(comment.comment_content)
}
}
.frame(alignment: .trailing)
.onAppear {
PostViewManager().getComments(intPostID: self.post.id){(comments) in
self.comments = comments
}
}
}
}
}
}
struct ViewPostView_Previews: PreviewProvider {
static var previews: some View {
ViewPostView()
}
}
/// Fetching data Code:
struct Comments: Codable, Identifiable {
var id: Int
var author_name: String
var comment_content: String
var comment_date: String
var comment_date_gmt: String
var approved: String
}
class PostViewManager {
func getComments(intPostID: Int, completion: #escaping ([Comments]) -> ()){
guard let url = URL(string: "https://test.matjri.com/wp-json/matjri/v1/comments/\(intPostID)") else {return}
URLSession.shared.dataTask(with: url) { (data, _, _) in
let comments = try! JSONDecoder().decode([Comments].self, from: data!)
DispatchQueue.main.async {
completion(comments)
}
}
.resume()
}
}
struct Posts: Codable, Identifiable {
var id: Int
var title: String
var city_id: Int
var city_name: String
var category_id: Int
var category_name: String
var since: String
var author_id: String
var author_name: String
var post_content: String
var featured_image: String
}
class ApiPosts {
func getPosts(intPage: Int, completion: #escaping ([Posts]) -> ()){
guard let url = URL(string: "https://test.matjri.com/wp-json/matjri/v1/posts/0") else {return}
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Posts].self, from: data!)
DispatchQueue.main.async {
completion(posts)
}
}
.resume()
}
}
The error you get "Preview struct, ERROR: Missing argument for parameter", typically is because you did not provide the required parameters to the Preview.
ViewPostView expect to be passed "var post: Posts", so in ViewPostView_Previews you
need to provide that, for example:
struct ViewPostView_Previews: PreviewProvider {
static var previews: some View {
ViewPostView(post: Posts(id: 1, title: "title", ... ))
}
}