I'm completing my Webview application, but I run into the following error:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
enter image description here
SwiftUIWebView(url: URL(string: "https://www.apple.com/"))
.navigationTitle("WebView")
}
}
}
struct ContenView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
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 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
}
}
}
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'm trying to figure out the navigation in the beginning of an app I'm making in the SwiftUI lifecycle. Depending on settings determined at launch, the user may start in one of three views. If the user opens the app from a push notification, they should see the video view (WrappedVideoViewController). Otherwise, if they are logged in already, they should see the Home view, or if not, the Login view. I'm using ContentView as the parent:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var notifService: NotificationService
#EnvironmentObject var authState: AuthenticationState
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ObservedObject var viewRouter: ViewRouter
init() {
self.viewRouter = ViewRouter.shared
}
var body: some View {
print("in ContentView body")
switch viewRouter.currentScreen {
case .home:
print("home")
return AnyView(Home(innerGradientColor: Color.homeActiveInner, outerGradientColor: Color.homeActiveOuter))
case .login:
print("login")
return
AnyView(Login()
.environmentObject(AuthenticationState.shared))
case .video:
print("video")
return AnyView(WrappedVideoViewController())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AuthenticationState.shared)
.environmentObject(NotificationService())
}
}
My ViewRouter class is an ObservableObject meant to control which screen is shown. Currently, I have this:
//
// NavigationState.swift
// BumpCall-SwiftUI
//
// Created by Ben on 1/22/21.
//
import Foundation
import SwiftUI
enum Screen {
case home
case video
case login
}
class ViewRouter: ObservableObject {
var authState: AuthenticationState
var notifService: NotificationService
static var shared = ViewRouter(authState: AuthenticationState.shared, notifService: NotificationService.shared)
init(authState: AuthenticationState, notifService: NotificationService) {
self.authState = authState
self.notifService = notifService
if notifService.dumbData != nil {
currentScreen = .video
} else if (notifService.dumbData == nil && authState.isLoggedIn()) {
currentScreen = .home
} else {
currentScreen = .login
}
}
#Published var currentScreen: Screen
}
It's tricky because the ViewRouter is dependent on two other ObservableObjects, which I'm trying to handle here with the init(). It appears that the AuthenticationState class is working fine because upon opening the app, it does send you to the home screen if you are logged in. However, opening from a notification also sends me to the home screen. NotificationService changes the ViewRouter singleton in the didReceive response: method:
class NotificationService: NSObject, ObservableObject {
#Published var dumbData: UNNotificationResponse?
static var shared = NotificationService()
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
}
extension NotificationService: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("presenting notification")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.notifService.dumbData = response
print("Notification service didReceive response")
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { }
}
But the singleton must not update for ContentView to notice. This is super convoluted and it's been confusing me for days, any help would be much appreciated.
The problem is that ViewRouter init() will be called once. As you mentioned, when the user is logged in, currentScreen will be initialized to .home. However this variable will never be changed afterwards.
What you basically want, is to set currentScreen to .video inside the Notification delegate.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.currentScreen = .video //<< here set video
print("Notification service didReceive response")
completionHandler()
}
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)
}
}