How to make API call in swiftUI - api

I am trying to use PokeApi to make a Pokedex app. I just started swift a couple of days ago so I'm following a tutorial here: https://www.youtube.com/watch?v=UsO-84Xnhww. The tutorial doesn't seem to work, and I don't know how to access the PokeAPI in order to make this app. My code is posted below:
ContentView:
import SwiftUI
struct ContentView: View {
#State var searchText = ""
var pokemon = [Pokemon]()
var body: some View {
NavigationView {
List{
ForEach(searchText == "" ? pokemon : pokemon.filter({
$0.name.contains(searchText.lowercased())
})) { entry in
HStack {
Circle() //Pokemon Image
NavigationLink("\(entry.name)".capitalized, destination: Text("Detail view for \(entry.name)"))
}
}
}
.onAppear {
PokemonManager().getData() { pokemon in self.pokemon = pokemon
for pokemon in pokemon {
print(pokemon.name)
}
}
}
.searchable(text: $searchText)
.navigationTitle("PokePass")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PokemonAPI:
import Foundation
struct CurrentPokemon: Codable {
var results: [Pokemon]
}
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
}
class PokemonManager {
func getData(completion: #escaping ([Pokemon]) -> ()) {
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=151") else {
return
}
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let pokemonList = try! JSONDecoder().decode(CurrentPokemon.self, from: data)
DispatchQueue.main.async {
completion(pokemonList.results)
}
}
.resume()
}
}

Use let id = UUID() in Pokemon, this will avoid decoding it, and that is what you want, since id is not part of the data.
You can also use this approach:
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
enum CodingKeys: String, CodingKey {
case name, url
}
}
EDIT-1
and use #State var pokemon = [Pokemon]() in ContentView

Related

Swift: Can't change class attribute from #objc function

I'm trying to capture ProximitySensor activity on SwiftUI.
So I've created a class ProximityOberver and trying to update the attribute 'state' in the notification:
import SwiftUI
import UIKit
class ProximityObserver {
#State var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
state = device.proximityState
print(state)
}
}
}
struct ContentView: View {
#State var proximityObserver = ProximityObserver()
func activateProximitySensor() {
print("MyView::activateProximitySensor")
if !UIDevice.current.isProximityMonitoringEnabled {
UIDevice.current.isProximityMonitoringEnabled = true
if UIDevice.current.isProximityMonitoringEnabled {
NotificationCenter.default.addObserver(proximityObserver, selector: #selector(proximityObserver.didChange), name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
}
}
func deactivateProximitySensor() {
print("MyView::deactivateProximitySensor")
UIDevice.current.isProximityMonitoringEnabled = false
NotificationCenter.default.removeObserver(proximityObserver, name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
var body: some View {
Text(proximityObserver.state ? "true" : "false" )
.animation(.linear(duration: 20).delay(20), value: proximityObserver.state)
.onAppear() {
self.activateProximitySensor()
}
.onDisappear() {
self.deactivateProximitySensor()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But even 'state = device.proximityState' code executed, the following print(state) shows the attribute never changed.
MyView::ProximityObserver.didChange
true
false
Can someone explain why this happens, and how to fix this?
Thank you for the comment.
I could fix this as suggested.
class ProximityObserver: ObservableObject {
#Published var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
self.state = device.proximityState
print(state, device.proximityState)
}
}
}
struct ContentView: View {
#ObservedObject var proximityObserver = ProximityObserver()
...

SwiftUI : How I can do POST API CALL with Combine?

Firstly, Please check my code.
This is Dataservice Utility
import Foundation
import Combine
import SwiftUI
class PapagoDataService {
#Published var translated : TranslateModel?
static let instance = PapagoDataService()
var cancellables = Set<AnyCancellable>()
private init() {
apiCall(text: "안녕하세요")
}
private func apiCall(text : String) {
let param = "source=ko&target=en&text=\(text)"
let paramData = param.data(using: .utf8)
let client_Id = "************"
let client_Secret = "********"
guard let url = URL(string: "https://openapi.naver.com/v1/papago/n2mt")
else { return }
print("0")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = paramData
request.addValue(client_Id, forHTTPHeaderField: "X-Naver-Client-Id")
print("1")
URLSession.shared.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global(qos: .background))
.tryMap(handleOutput)
.decode(type: TranslateModel.self, decoder: JSONDecoder())
.sink { (completion) in
switch completion {
case .finished:
break
case .failure(let error):
print("Error to download")
}
} receiveValue: { [weak self] (translatedData) in
self?.translated = translatedData
}
.store(in: &cancellables)
}
func handleOutput(output : URLSession.DataTaskPublisher.Output) throws -> Data {
guard let response = output.response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else {
throw URLError(.badServerResponse)
}
return output.data
}
}
Second one is ViewModel
import Foundation
import Combine
class HomeViewModel : ObservableObject {
#Published var translated : TranslateModel?
let dataService = PapagoDataService.instance
var cancellable = Set<AnyCancellable>()
init() {
addSubscriber()
}
private func addSubscriber() {
dataService.$translated
.sink { [weak self] (receiveModel) in
DispatchQueue.main.async {
self?.translated = receiveModel
}
}
.store(in: &cancellable)
}
}
Last, This is Model of data
import Foundation
struct TranslateModel : Codable {
let message: Message
}
// MARK: - Message
struct Message: Codable {
let type, service, version: String
let result: Result
enum CodingKeys: String, CodingKey {
case type = "#type"
case service = "#service"
case version = "#version"
case result
}
}
// MARK: - Result
struct Result: Codable {
let translatedText: String
}
I tried to do POST API Call with combine by this way.
But, I got the error on DataService Class
Check this Picture please.
In my opinion, URLsession is wrong, but I don't know what is the problem.
I hope I can serve this problem to understand api call with combine!
Thank you.
in your private func apiCall(text : String) {...} use the request, like this:
URLSession.shared.dataTaskPublisher(for: request) // <--- here
PS: do not show your client_Secret in your code, remove it now.

How to change the page programmicly in swiftui pdf

I have a code to display a pdf
import PDFKit
import SwiftUI
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let singlePage: Bool
var doc:PDFView=PDFView()
init(_ data: Data, singlePage: Bool = false) {
self.data = data
self.singlePage = singlePage
}
func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = doc
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
struct ContentView: View {
var path=Bundle.main.url(forResource: "big", withExtension: "pdf");
#State var doc:PDFKitRepresentedView=PDFKitRepresentedView(Data())
#State var data :Data?;
var body: some View {
HStack{
start(doc: &doc,path: path!)
.onAppear(perform: {
self.data=try?Data(contentsOf: path!);
doc.doc.goToNextPage(nil)
doc.doc.goToNextPage(nil)
})
}
}
}
func start(doc:inout PDFKitRepresentedView,path:URL)->PDFKitRepresentedView{
doc=try!PDFKitRepresentedView(Data(contentsOf: path));
return doc;
}
But I can't seem to find the method to change the page in the showing pdf.I tried go() but it just crashed. I need a button to change the page on click.Sorry I'm new to swiftUI.
you could re-structure your code and use the following approach in adding the page selection from the Buttons, into func updateUIView(...) :
import PDFKit
import SwiftUI
struct PDFKitView: UIViewRepresentable {
typealias UIViewType = PDFView
#Binding var page: Int
#State var data: Data
let singlePage: Bool
func makeUIView(context: UIViewRepresentableContext<PDFKitView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ view: UIViewType, context: UIViewRepresentableContext<PDFKitView>) {
if let thePage = view.document?.page(at: page) {
view.go(to: thePage)
}
}
}
struct ContentView: View {
#State var page = 0
var body: some View {
VStack {
HStack {
Button("Next Page") { page += 1 } // need to add bounds check
Button("Prev Page") { page -= 1 } // need to add bounds check
}.buttonStyle(.bordered)
PDFKitView(page: $page, data: loadData(), singlePage: true)
}
}
private func loadData() -> Data {
guard let path = Bundle.main.url(forResource: "big", withExtension: "pdf") else {
print("Could not find PDF document")
return Data()
}
do {
return try Data(contentsOf: path)
} catch {
print("error: \(error)") // todo
return Data()
}
}
}
EDIT-1:
Another somewhat more flexible approach is to pass a PDFDocument into PDFKitView, such as:
struct PDFKitView: UIViewRepresentable {
typealias UIViewType = PDFView
#Binding var page: Int
#Binding var doc: PDFDocument
let singlePage: Bool
func makeUIView(context: UIViewRepresentableContext<PDFKitView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = doc
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ view: UIViewType, context: UIViewRepresentableContext<PDFKitView>) {
view.document = doc
if let thePage = view.document?.page(at: page) {
view.go(to: thePage)
}
}
}
struct ContentView: View {
#State var page = 0
#State var doc = PDFDocument()
var body: some View {
VStack {
HStack {
Button("Next Page") {
if page + 1 < doc.pageCount {
page += 1
}
}
Button("Prev Page") {
if page-1 > 0 {
page -= 1
}
}
}.buttonStyle(.bordered)
PDFKitView(page: $page, doc: $doc, singlePage: true)
}
.onAppear {
loadDoc()
}
}
private func loadDoc() {
guard let path = Bundle.main.url(forResource: "big", withExtension: "pdf") else {
print("Could not find PDF document")
return
}
do {
let data = try Data(contentsOf: path)
if let theDoc = PDFDocument(data: data) {
doc = theDoc
}
} catch {
print("error: \(error)") // todo
}
}
}

How to check if #EnvironmentObject is set? (SwiftUI)

What I have: View and ViewModel (as extension to View struct).
Both of them are use #EnvironmentObject of type AppState.
The problem is that my preview crashes due to this error:
Fatal error: No ObservableObject of type AppState found.
Commenting out lines in loadUser func saves from crash.
struct ProfileView: View {
#EnvironmentObject var appState: AppState
#ObservedObject var viewModel = ViewModel()
...
}
extension ProfileView {
class ViewModel: ObservableObject {
#EnvironmentObject var appState: AppState
#Published var userVM = UserVM(.example)
init() {
loadUser()
}
func loadUser() {
User.WebService.getSelf { user, errorMsg in
DispatchQueue.main.async {
guard let user = user else {
/*self.appState.showingAlert = true
self.appState.alert = Alert(
title: Text("An error occured!"),
message: Text(errorMsg ?? "unknown error"))*/
return
}
self.userVM = UserVM(user)
}
}
}
}
}
struct ProfileView_Previews: PreviewProvider {
static let viewModel = ProfileView.ViewModel()
static var previews: some View {
let appState = AppState()
appState.activeScreen = .profile
return ProfileView()
.environmentObject(appState)
}
}

Observing Firebase Authenfication with ObservableObject

I am trying to observe firebase authentification and update my View accordingly.
I have an SessionStore object:
class SessionStore: ObservableObject {
#Published var session: Account?
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
I use it in a view like this:
struct TabBarView: View {
#EnvironmentObject var sessionStore: SessionStore
#State var selectedTab = Tab.swiping
enum Tab: Int {
case swiping, matches, profil
}
func getUser() {
sessionStore.listen()
}
var body: some View {
Group {
if (sessionStore.session != nil) {
TabView(selection: $selectedTab) {
SwipingView().tabItem {
TabBarItem(text: "Text", image: "pause.circle")
}.tag(Tab.swiping)
}
} else {
LoginView()
}
}.onAppear(perform: getUser).onDisappear(perform: sessionStore.stopListen)
}
}
And call it like this:
sessionStore = SessionStore()
TabBarView().environmentObject(sessionStore!)
But it is only showing the LoginView even when the session is not nil. I made some code changes this is actually the solution.
I think this is the way to do that
class SessionStore: ObservableObject {
#Published var session: Account?
Also you referenced self inside the state closure meaning your object will never deinit. Add unowned or weak like so:
handle = Auth.auth().addStateDidChangeListener { [unowned self] (auth, user) in
https://www.avanderlee.com/swift/weak-self/
The correct implementation is:
class SessionStore: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var session: Account? {
didSet {
objectWillChange.send()
}
}
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
More info about it:
https://www.pointfree.co/blog/posts/30-swiftui-and-state-management-corrections