How to change the page programmicly in swiftui pdf - 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
}
}
}

Related

UICollectionViewCell button callback

How to implement callback for example of button tap inside UICollectionViewCell's UIContentView or better how to do it in Combine way?
Cell registration inside UICollectionView:
let cellRegistration = UICollectionView.CellRegistration<MyCell, Item> { (cell, indexPath, item) in
cell.item = item
}
Cell:
class MyCell: UICollectionViewCell {
var item: Item?
override func updateConfiguration(using state: UICellConfigurationState) {
var newConfiguration = MyContentConfiguration().updated(for: state)
newConfiguration.name = item?.title
contentConfiguration = newConfiguration
}
}
Content configuration:
struct MyContentConfiguration: UIContentConfiguration, Hashable {
var name: String?
func makeContentView() -> UIView & UIContentView {
return MyContentView(configuration: self)
}
}
Content view:
class MyContentView: UIView, UIContentView {
let title = UILabel()
private var currentConfiguration: MyContentConfiguration!
var configuration: UIContentConfiguration {
get {
currentConfiguration
}
set {
guard let newConfiguration = newValue as? MyContentConfiguration else {
return
}
apply(configuration: newConfiguration)
}
}
init(configuration: MyContentConfiguration) {
super.init(frame: .zero)
// Create the content view UI
setupUI()
apply(configuration: configuration)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension MyContentView {
private func setupUI() {
// UI stuff
}
private func apply(configuration: MyContentConfiguration) {
guard currentConfiguration != configuration else {
return
}
currentConfiguration = configuration
// Set data to UI elements
title.text = title
//etc..
}
}
Inside MyContentView there'll be a button for example. This button should be bound with item, so when it's tapped - some callback or Combine publisher should emit value, which I've got to catch inside my collection view.
How I've implemented this previously:
let cellRegistration = UICollectionView.CellRegistration<MyCell, Item> { (cell, indexPath, item) in
cell.item = item
//Catch value which is bound with associated item
cell.somePublisher
.sink { [weak self] in
guard let self = self else { return }
self.subscribePublisher.send($0)
}
.store(in: &self.subscriptions)
}
Cell:
class MyCell: UICollectionViewCell {
var item: Item?
public private(set) var somePublisher = CurrentValueSubject<Bool?, Never>(nil)
//UI setup, etc..
//Emit value for a button tap gesture
#objc func handleTap() {
somePublisher.send(true)
}
}
Is it possible to implement this behavior using UIContentView & UIContentConfiguration?

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 Dynamic CloudKIt predicate

I am trying to make a dynamic predicate for Cloudkit to return items that match the segmented picker control. so for example
let predicate = NSPredicate(format: "department == %#", "Marketing") the 'marketing' part of the predicate would be a String variable which changes depending on the segmented picker control. I have tried #State #Published but none work. I have Googled Cloudkit dynamic predicates but there is little out there. Any help would be gratefully received.
This is my code
class staffCloudKitViewModel: ObservableObject {
#Published var text: String = ""
#Published var staff: [StaffModel] = []
#Published var predicateString: String = ""
init(){
fetchItems()
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(red: 116/255,
green: 152/255, blue: 192/255, alpha: 1.0)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor:
UIColor.white], for: .selected)
}
func fetchItems() {
//let predicate = NSPredicate(value: true)
let predicate = NSPredicate(format: "department == %#", "Marketing")
let query = CKQuery(recordType: "Staff", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
var returnedItems: [StaffModel] = []
queryOperation.recordMatchedBlock = { (returnedRecordID, returnedResult) in
switch returnedResult {
case .success(let record):
guard let firstName = record["firstName"] as? String else { return }
guard let lastName = record["lastName"] as? String else { return }
guard let department = record["department"] as? String else { return }
guard let country = record["country"] as? String else { return }
guard let mobile = record["phone"] as? String else { return }
guard let job = record["job"] as? String else { return }
let imageAsset = record["headshot"] as? CKAsset
let imageURL = imageAsset?.fileURL
returnedItems.append(StaffModel(firstName: firstName, lastName: lastName,
country: country, department: department, phoneNumber: mobile, job: job,
headshot:
imageURL, record: record))
case .failure(let error):
print("Error recordMatchedBlock: \(error)")
}
}
queryOperation.queryResultBlock = { [weak self] returnedResult in
print("RETURNED RESULT \(returnedResult)")
DispatchQueue.main.async {
self?.staff = returnedItems
}
}
addOperation(opertaion: queryOperation)
}
func addOperation(opertaion: CKDatabaseOperation) {
CKContainer.default().publicCloudDatabase.add(opertaion)
}
}
struct ContentView: View {
#State private var countryselected: Country = .UK
#StateObject private var vm = staffCloudKitViewModel()
var body: some View {
NavigationView {
VStack {
Picker("Select country", selection: $countryselected) {
ForEach(Country.allCases, id: \.self){
Text($0.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()
List {
ForEach(vm.staff, id: \.self) { thepeople in
VStack (alignment: .leading) {

How to make API call in swiftUI

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

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)
}
}