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.
Related
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()
...
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
I am creating my first kotlin multiplataform project, and i'm having some difficulty to use the kotlin flow on swift. I created the models, server data requests as common file using kotlin flow and ktor, the the view model and ui layers i am creating as native. So, i have no experience with swift development, and beyond that i' having a lot of trouble to use flow on swift view model. Searching for an answer to my problem i found a class described as CommonFlow, which aims to be used as a common code for both languages(kotlin, swift, but i'm having an error that gives me little or no clue as to why it happens or, probably, it's just my lack of dominion with xcode and swift programming:
So this is the code part that xcode points the error:
Obs: sorry about the image i thought that maybe this time it would be more descriptive
And this its all i got from the error
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5928ff8)
MY Ios ViewModel:
class ProfileViewModel: ObservableObject {
private let repository: ProfileRepository
init(repository: ProfileRepository) {
self.repository = repository
}
#Published var authentication: Authetication = Authetication.unauthenticated(false)
#Published var TokenResponse: ResponseDTO<Token>? = nil
#Published var loading: Bool = false
func authenticate(email: String, password: String) {
DispatchQueue.main.async {
if(self.isValidForm(email: email, password: password)){
self.repository.getTokenCFlow(email: email, password: password).watch{ response in
switch response?.status {
case StatusDTO.success:
self.loading = false
let token: String = response!.data!.accessToken!
SecretStorage().saveToken(token: token)
self.authentication = Authetication.authenticated
break;
case StatusDTO.loading:
self.loading = true
break;
case StatusDTO.error:
print("Ninja request error \(String(describing: response!.error!))}")
break;
default:
break
}
}
}
}
}
private func isValidForm(email: String, password: String) -> Bool {
var invalidFields = [Pair]()
if(!isValidEmail(email)){
invalidFields.append(Pair(first:"email invalido",second: "email invalido"))
}
if(password.isEmpty) {
invalidFields.append(Pair(first:"senha invalida",second: "senha invalida"))
}
if(!invalidFields.isEmpty){
self.authentication = Authetication.invalidAuthentication(invalidFields)
return false
}
return true
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
}
class Pair {
let first: String
let second: String
init(first:String, second: String) {
self.first = first
self.second = second
}
}
enum Authetication {
case invalidAuthentication([Pair])
case authenticated
case persistentAuthentication
case unauthenticated(Bool)
case authenticationFailed(String)
}
The repository methods:
override fun getToken(email: String, password: String): Flow<ResponseDTO<Token>> = flow {
emit(ResponseDTO.loading<Token>())
try {
val result = api.getToken(GetTokenBody(email, password))
emit(ResponseDTO.success(result))
} catch (e: Exception) {
emit(ResponseDTO.error<Token>(e))
}
}
#InternalCoroutinesApi
override fun getTokenCFlow(email: String, password: String): CFlow<ResponseDTO<Token>> {
return wrapSwift(getToken(email, password))
}
The Class CFLOW:
#InternalCoroutinesApi
class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object: Closeable {
override fun close() {
job.cancel()
}
}
}
}
#FlowPreview
#ExperimentalCoroutinesApi
#InternalCoroutinesApi
fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())
#InternalCoroutinesApi
fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)
#InternalCoroutinesApi
fun <T> wrapSwift(flow: Flow<T>): CFlow<T> = CFlow(flow)
There is an example of using flows in KampKit
https://github.com/touchlab/KaMPKit
I will paste an excerpt from NativeViewModel (iOS)
class NativeViewModel(
private val onLoading: () -> Unit,
private val onSuccess: (ItemDataSummary) -> Unit,
private val onError: (String) -> Unit,
private val onEmpty: () -> Unit
) : KoinComponent {
private val log: Kermit by inject { parametersOf("BreedModel") }
private val scope = MainScope(Dispatchers.Main, log)
private val breedModel: BreedModel = BreedModel()
private val _breedStateFlow: MutableStateFlow<DataState<ItemDataSummary>> = MutableStateFlow(
DataState.Loading
)
init {
ensureNeverFrozen()
observeBreeds()
}
#OptIn(FlowPreview::class)
fun observeBreeds() {
scope.launch {
log.v { "getBreeds: Collecting Things" }
flowOf(
breedModel.refreshBreedsIfStale(true),
breedModel.getBreedsFromCache()
).flattenMerge().collect { dataState ->
_breedStateFlow.value = dataState
}
}
This ViewModel is consumed in swift like this:
lazy var adapter: NativeViewModel = NativeViewModel(
onLoading: { /* Loading spinner is shown automatically on iOS */
[weak self] in
guard let self = self else { return }
if (!(self.refreshControl.isRefreshing)) {
self.refreshControl.beginRefreshing()
}
},
onSuccess: {
[weak self] summary in self?.viewUpdateSuccess(for: summary)
self?.refreshControl.endRefreshing()
},
onError: { [weak self] error in self?.errorUpdate(for: error)
self?.refreshControl.endRefreshing()
},
onEmpty: { /* Show "No doggos found!" message */
[weak self] in self?.refreshControl.endRefreshing()
}
)
In short, the flow is kept wrapped in kotlin mp land and leveraged in iOS by using traditional callback interfaces.
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)
}
}
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