hello i want to display a PDF file at page 7 in my SwiftUI app.
I would also like to use the functions .go(to :) and .currentPage() outside the UIViewRepresentable.
import SwiftUI
import PDFKit
struct PDFKitView: View {
var pdf: PDF
var body: some View {
PDFKitRepresentedView(pdf)
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let pdf: PDF
init(_ pdf: PDF) {
self.pdf = pdf
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
let document = PDFDocument(data: pdf.content!)
pdfView.document = document
pdfView.autoScales = true
print("pdfView.currentPage: \(String(describing: pdfView.currentPage))") // pdfView.currentPage: Optional(<PDFPage: 0x600002245420> page index 0)
print("pdfView: \(pdfView)") // pdfView: <PDFView: 0x7fb206526c20; frame = (0 0; 0 0); gestureRecognizers = <NSArray: 0x600002ef4150>; layer = <CALayer: 0x60000202ddc0>>
if let myPage = document?.page(at: 7) {
pdfView.go(to: myPage)
}
print("pdfView.currentPage: \(String(describing: pdfView.currentPage))") // pdfView.currentPage: Optional(<PDFPage: 0x6000022455c0> page index 7)
print("pdfView: \(pdfView)") // pdfView: <PDFView: 0x7fb206526c20; frame = (0 0; 0 0); gestureRecognizers = <NSArray: 0x600002ef4150>; layer = <CALayer: 0x60000202ddc0>>
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
The PDF file is shown but at page 1 and not 7 :/
I'm still relatively new to it Swift, can anyone give me a hint?
this is how it works now
the problem was
PDFView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
and not
PDFView()
here the complete code
import SwiftUI
import PDFKit
struct PDFKitView: View {
#State var pdf: PDF
#Binding var pageIndex: Int
var body: some View {
VStack{
PDFPreviewController(pdfX: $pdf , pageIndex: $pageIndex )
}
}
}
class PDFPreviewViewConroller: UIViewController {
public var pdfView: PDFView!
override func loadView() {
pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
self.view = pdfView
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
struct PDFPreviewController: UIViewControllerRepresentable {
#Binding var pdf: PDF
#Binding var pageIndex: Int
init(pdfX: Binding<PDF>, pageIndex: Binding<Int>) {
_pdf = pdfX
_pageIndex = pageIndex
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PDFPreviewController>) -> PDFPreviewViewConroller {
return PDFPreviewViewConroller()
}
func updateUIViewController(_ uiViewController: PDFPreviewViewConroller, context: UIViewControllerRepresentableContext<PDFPreviewController>) {
uiViewController.pdfView.document = PDFDocument(data: pdf.content!)
if let myPage = uiViewController.pdfView.document?.page(at: (pageIndex)) {
uiViewController.pdfView.go(to: myPage)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(pdf: $pdf, pageIndex: $pageIndex)
}
class Coordinator: NSObject {
#Binding var pageIndex: Int
#Binding var pdf: PDF
init(pdf: Binding<PDF>, pageIndex: Binding<Int>) {
_pageIndex = pageIndex
_pdf = pdf
}
}
}
Related
It seems like there's a bug in iOS 16 where a UISearchBar in a UISplitViewController's primary position displays with a square background. This doesn't happen if the search bar is in other positions, or if the split view is collapsed (eg. on iPhone).
I've reported it (FB10847490) but any ideas how I could work around this in the meantime? It seems like .background/.backgroundColor and searchTextField.background/backgroundColor both affect other subviews and not the view that is causing the square appearance.
Sample app:
struct ContentView: View {
var body: some View {
HostingController()
}
}
struct HostingController: UIViewControllerRepresentable {
#State private var text = ""
func makeUIViewController(context: Context) -> some UIViewController {
let controller = UISplitViewController(style: .doubleColumn)
let contentView = UIHostingController(rootView: VStack {
SearchBar(text: $text)
})
let primaryController = UINavigationController(rootViewController: contentView)
controller.setViewController(primaryController, for: .primary)
controller.setViewController(UIHostingController(rootView: SearchBar(text: $text)), for: .secondary)
controller.preferredSplitBehavior = .overlay
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
struct SearchBar: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UISearchBar {
let searchBar = UISearchBar()
searchBar.placeholder = "Search..."
searchBar.returnKeyType = .done
searchBar.enablesReturnKeyAutomatically = false
searchBar.searchBarStyle = .minimal
searchBar.text = text
searchBar.searchBarStyle = .minimal
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: Context) {
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPad")
}
}
I am using SwiftUI, so I have a wrapper around the Webview, like this:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: String
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
print(result as! String)
}
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string:url)!)
uiView.load(request)
}
}
How can I execute a function/method whenever the user makes scroll to the website?
To get scroll position updates you need to create coordinator and override makeCoordinator() method and return instance of your coordinator. In makeUIView(_:) method just assign scrollview delegate to context.coordinator (context object in provided in arguments of makeUIView method)
Pass binding from view to coordinator and coordinator is responsible to update that binding. Here is code for that
struct WebView: UIViewRepresentable {
var url: String
#Binding var contentOffset: CGPoint
init(url: String, contentOffset: Binding<CGPoint>) {
self.url = url
_contentOffset = contentOffset
}
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
webView.scrollView.delegate = context.coordinator // assign delegation
webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
print(result as! String)
}
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string:url)!)
uiView.load(request)
}
func makeCoordinator() -> Coordinator {
.init(contentOffset: $contentOffset) // create coordinator for delegation
}
class Coordinator: NSObject, UIScrollViewDelegate {
#Binding var contentOffset: CGPoint
init(contentOffset: Binding<CGPoint>) {
_contentOffset = contentOffset
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
contentOffset = scrollView.contentOffset
}
}
}
WebView with UIScrollViewDelegate support.
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: URL
var scrollViewDelegate: UIScrollViewDelegate?
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.scrollView.delegate = scrollViewDelegate
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}}
Observable ScrollViewDetector
import UIKit.UIScrollView
final class ScrollViewDetector: NSObject, ObservableObject, UIScrollViewDelegate {
#Published var isScrolledEnd = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentLoaded = scrollView.contentSize.height > 1000
let endOfContentReached = scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.size.height < 100
if contentLoaded && endOfContentReached {
isScrolledEnd = true
}
}}
Agreement view with sticky footer button.
import SwiftUI
struct AgreementView: View {
#StateObject var scrollViewDetector = ScrollViewDetector()
var body: some View {
GeometryReader { geometry in
ZStack {
WebView(url: URL(string: "YOUR URL")!, scrollViewDelegate: scrollViewDetector)
// Footer Navigation Link
NavigationLink {
EmptyView()
} label: {
// Your Label
Button("Accept")
.padding()
.background(Color.white.opacity(!scrollViewDetector.isScrolledEnd ? 0.9 : 0.0))
}
.position(x: geometry.width(0.5), y: geometry.height(0.9))
.opacity(!scrollViewDetector.isScrolledEnd ? 0.6 : 1.0)
.disabled(!scrollViewDetector.isScrolledEnd ? true : false)
}
}
}}
Take a look at the screenshot:
I marked the "Top Navigation Bar" red, which I want to remove, as there is an unused top bar...
You have to know that I code using Storyboards, but this specific page is holding a subview of SwiftUI View!
This is the SwiftUI ContentView:
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
NavigationView{
MasterView()
}.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
struct MasterView: View {
var body: some View {
Form {
Section(header: Text("Geplant")) {
Section {
NavigationLink(destination: UIKitView()) { Text("Berlin") }
}
}
}
.navigationBarTitle("Wohin gehts?")
}
}
struct UIKitView: UIViewControllerRepresentable {
typealias UIViewControllerType = SwipeViewController
func makeUIViewController(context: Context) -> SwipeViewController {
let sb = UIStoryboard(name: "Storyboard", bundle: nil)
let viewController = sb.instantiateViewController(identifier: "swipe") as! SwipeViewController
return viewController
}
func updateUIViewController(_ uiViewController: SwipeViewController, context: Context) {
}
}
And this is the UIViewController, which is holding the SwiftUI Subview:
import UIKit
import SwiftUI
class StartViewController: UIViewController {
#IBOutlet weak var btn: UIButton!
#IBOutlet weak var container: UIView!
let contentView = UIHostingController(rootView: ContentView())
override func viewDidLoad() {
super.viewDidLoad()
configureBackgroundGradient()
addChild(contentView)
view.addSubview(contentView.view)
setupContraints()
}
fileprivate func setupContraints(){
contentView.view.translatesAutoresizingMaskIntoConstraints = false
contentView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
private func configureBackgroundGradient() {
let backgroundGray = UIColor(red: 244 / 255, green: 247 / 255, blue: 250 / 255, alpha: 1)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.white.cgColor, backgroundGray.cgColor]
gradientLayer.frame = view.bounds
view.layer.insertSublayer(gradientLayer, at: 0) //Background Color
}
}
Can anyone can help? :))
Thank you! Feel free to ask me for more screenshots or code!
You can show a view in full screen with the SwiftUI view modifiere fullScreenCover. https://www.hackingwithswift.com/quick-start/swiftui/how-to-present-a-full-screen-modal-view-using-fullscreencover
Let us take simple FullScreenModalView struct that can dismiss itself, then presents it from ContentView when another button is pressed:
struct FullScreenModalView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Dismiss Modal") {
presentationMode.wrappedValue.dismiss()
}
}
}
And here is the code for ContentView -
struct ContentView: View {
#State private var isPresented = false
var body: some View {
Button("Present!") {
isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}
}
Happy to help.
Thanks.
I have a UIViewController working fine in SwiftUI with the below code.
import Foundation
import SwiftUI
final class RTCVideoViewController: UIViewController {
var previewView: RTCEAGLVideoView!
var videoTrack: RTCVideoTrack!
override func viewDidLoad() {
previewView = RTCEAGLVideoView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
view.addSubview(previewView)
do {
videoTrack = try MediaCapturer.shared.createVideoTrack(videoView: previewView)
} catch {
print(error)
}
}
}
extension RTCVideoViewController : UIViewControllerRepresentable{
public typealias UIViewControllerType = RTCVideoViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<RTCVideoViewController>) -> RTCVideoViewController {
return RTCVideoViewController()
}
public func updateUIViewController(_ uiViewController: RTCVideoViewController, context: UIViewControllerRepresentableContext<RTCVideoViewController>) {
}
}
In SwiftUI view.
import Combine
import SwiftUI
struct LiveView: View {
#ObservedObject var viewModel: LiveViewModel
init(viewModel: LiveViewModel) {
self.viewModel = viewModel
}
var body: some View {
return ZStack {
RTCVideoViewController()
.edgesIgnoringSafeArea(.top)
}
}
}
So far everything works fine but I need the RTCVideoTrack when created to pass it to the LiveViewModel.
Any ideas what pattern I could implement to get notified when viewDidLoad finished or RTCVideoTrack passed to the LiveViewModel?
I changed a bit the code, it was close but simplified more.
import Foundation
import SwiftUI
struct RTCVideoViewController : UIViewControllerRepresentable {
var viewModel: LiveViewModel
func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {
print("HostBroadcastViewController:updateUIViewController")
let previewView = RTCEAGLVideoView(frame: CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
viewController.view.addSubview(previewView)
do {
let videoTrack = try MediaCapturer.shared.createVideoTrack(videoView: previewView)
self.viewModel.videoTrack = videoTrack
}
catch {
print("ERROR getting RTCVideoTrack")
}
}
}
And in SwiftUI init the RTCVideoViewController with RTCVideoViewController(viewModel: self.viewModel) where ViewModel passed in view from parent.
I'm trying to observe/get some of SwiftUI's List scrolling attributes by wrapping/injection of UIScrollView using UIViewRepresentable.
I'm getting inconsistent behavior with bindings.
Pressing the buttons and changing values depending on if the button is in the parent vs child view has different results.
The bindings from my ObservableObject ScrollInfo class and the UIViewRepresentable start fine, but then break, unless the whole screen is refreshed and makeUIView runs again (like changing to a different tab).
Is there a way to force the UIViewRepresentable to run makeUIView again on a binding update? Or something that will fix this?
I'd like for isScrolling values to be updated and working all the time.
I set up a test to change the colors of the circles to red if the user is dragging the scrollview down. It works initially but stops if I update a value from the ObservableObject in the parent view.
Screenshots of Test from code below
Bindings keep working with bottom button press (updating ObservableObject) in child view
Bindings break with top button press (updating ObservableObject) in parent view
// Parent View
import SwiftUI
struct ContentView: View {
#ObservedObject var scrollInfo:ScrollInfo = ScrollInfo()
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded = true;
})
{
Text("REFRESH")
}
TestView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Child View
import SwiftUI
struct TestView: View {
#State var test1:String = "Test1"
#ObservedObject var scrollInfo:ScrollInfo = ScrollInfo()
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded.toggle()
}) {
Text("REFRESH")
}
List{
VStack {
VStack{
Text(String( self.scrollInfo.contentLoaded))
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.scrollInfo.isScrolling ? .red : .green)
}
} .padding(.bottom,620).padding(.top,20)
.background(
ListScrollingHelper(scrollInfo: self.scrollInfo)// injection
)
}.padding(.top,4).onAppear(perform:{
})
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
// ScrollInfo Class
class ScrollInfo: ObservableObject {
#Published var isScrolling:Bool = false
#Published var text:String = "Blank"
#Published var contentLoaded:Bool = false
init()
{
print( "scrollInfo init")
}
deinit {
print("scrollInfo denit")
}
}
// UIViewRepresentable
final class ListScrollingHelper: UIViewRepresentable {
var scrollInfo:ScrollInfo
#Published var scrollView: UIScrollView?
init( scrollInfo:ScrollInfo) {
print("init UIViewRepresentable listscrolling helper")
self.scrollInfo = scrollInfo
}
func makeUIView(context: Context) -> UIView {
//self.uiScrollView.delegate = context.coordinator
print("makeview")
return UIView()
//return self.uiScrollView // managed by SwiftUI, no overloads
}
func catchScrollView(for view: UIView) {
print("checking for scrollview")
if nil == scrollView {
scrollView = view.enclosingScrollView()
if(scrollView != nil)
{
print("scrollview found")
}
}
}
func updateUIView(_ uiView: UIView, context: Context) {
catchScrollView(for: uiView)
if(scrollView != nil)
{
scrollView!.delegate = context.coordinator
}
print("updatingUIView")
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,UIScrollViewDelegate {
var parent: ListScrollingHelper
init(_ listScrollingHelper: ListScrollingHelper) {
self.parent = listScrollingHelper
print("init coordinator")
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// DispatchQueue.main.async {
if(!self.parent.scrollInfo.isScrolling)
{
self.parent.scrollInfo.isScrolling = true
//self.parent.scrollInfo.text = "scroll"
// }
}
print("start scroll")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(self.parent.scrollInfo.isScrolling && !decelerate)
{
self.parent.scrollInfo.isScrolling = false
}
print("end scroll")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if(self.parent.scrollInfo.isScrolling)
{
self.parent.scrollInfo.isScrolling = false
}
print("end scroll")
}
deinit
{
print("deinit coordinator")
}
}
deinit {
print("deinit UIViewRepresentable listscrolling helper")
}
}
extension UIView {
func enclosingScrollView() -> UIScrollView? {
var next: UIView? = self
repeat {
next = next?.superview
if let scrollview = next as? UIScrollView {
return scrollview
}
} while next != nil
return nil
}
}
EDIT - WORKAROUND
I wasn't able to get it working with the ObservableObject or EnvironmentObject, but I was able to get it working with #State and #Binding, although it's a limited amount of info passed back. (ScrollInfo is still there only to use to testing changing a parent ObservableObject)
Hope this helps someone else!
import SwiftUI
struct TestView: View {
#State var isScrolling:Bool = false;
var body: some View {
VStack
{
Button(action:{
self.scrollInfo.contentLoaded.toggle()
}) {
Text("REFRESH")
}
List{
VStack {
VStack{
Text("isScrolling")
Text(String(self.isScrolling))
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
VStack{
Text(self.scrollInfo.text)
Text(self.test1)
Circle().frame(width:50,height:50).foregroundColor(self.isScrolling ? .red : .green)
}
} .padding(.bottom,620).padding(.top,20).background( ListScrollingHelper(isScrolling: self.$isScrolling))
}.padding(.top,4)
}
}
}
extension UIView {
func enclosingScrollView() -> UIScrollView? {
var next: UIView? = self
repeat {
next = next?.superview
if let scrollview = next as? UIScrollView {
return scrollview
}
} while next != nil
return nil
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
final class ListScrollingHelper: UIViewRepresentable {
#Binding var isScrolling:Bool
private var scrollView: UIScrollView?
init(isScrolling:Binding<Bool>)
{
self._isScrolling = isScrolling
}
func makeUIView(context: Context) -> UIView {
return UIView()
}
func catchScrollView(for view: UIView) {
if nil == scrollView {
scrollView = view.enclosingScrollView()
}
}
func updateUIView(_ uiView: UIView, context: Context) {
catchScrollView(for: uiView)
if(scrollView != nil)
{
scrollView!.delegate = context.coordinator
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,UIScrollViewDelegate {
var parent: ListScrollingHelper
init(_ listScrollingHelper: ListScrollingHelper) {
self.parent = listScrollingHelper
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if(!self.parent.isScrolling)
{
self.parent.isScrolling = true
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(self.parent.isScrolling && !decelerate)
{
self.parent.isScrolling = false
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if(self.parent.isScrolling)
{
self.parent.isScrolling = false
}
}
deinit
{
}
}
deinit {
}
}
class ScrollInfo: ObservableObject {
#Published var isScrolling:Bool = false
#Published var text:String = "Blank"
#Published var contentLoaded:Bool = false
}