How do I display a MathJax String in a SwiftUI ScrollView? - swift

I receive a MathJax String from an API response which needs to be shown in a WKWebView in SwiftUI. Here's a sample String
"<img alt=\"\" height=\"2305\" src=\"https:\/\/s3-us-west-2.amazonaws.com\/infinitestudent-images\/ckeditor_assets\/pictures\/37732\/content_types_of_combustion.jpg\" width=\"800\" \/>"
Here's the code that I've tried:
import SwiftUI
import WebKit
struct Model: Codable {
var mathjaxBody: String?
var answer: Answer
}
struct MathJaxView: View {
let model: Model
var body: some View {
ScrollView {
AnswerSectionView(answer: model.answer)
HTMLView(htmlString: model.mathjaxBody)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
}
}
}
struct HTMLView: UIViewRepresentable {
var htmlString: String?
func makeUIView(context: Context) -> WKWebView {
WKWebView(frame: .zero)
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let string = htmlString else { return }
uiView.loadHTMLString(string, baseURL: nil)
}
}
It need to be in a ScrollView with a few different details on top of it, which is displayed properly in the ScrollView. But, the HTMLView doesn't show up. I tried adding .frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity) But still doesn't show up.

The solution was to simply calculate the required height from evaluateJavaScript("document.body.scrollHeight") method and apply it to the WebView component with a few other methods.
struct WebView: UIViewRepresentable {
var htmlString: String?
#EnvironmentObject var viewModel: WebViewModel
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView(frame: .zero)
webView.backgroundColor = .clear
webView.navigationDelegate = context.coordinator
webView.scrollView.isScrollEnabled = false
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let string = htmlString else { return }
uiView.loadHTMLString(string, baseURL: nil)
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState") { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.body.scrollHeight") { (height, error) in
if let height = height as? CGFloat, self.viewModel.contentHeight == 0 {
self.viewModel.contentHeight = height * 0.4
}
}
}
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(viewModel: viewModel)
}
}
class WebViewModel: ObservableObject {
#Published var contentHeight: CGFloat = 0
}
struct MathJaxView: View {
let model: Model
var body: some View {
ScrollView {
AnswerSectionView(answer: model.answer)
WebView(htmlString: model.mathjaxBody)
.environmentObject(webViewModel)
.frame(height: webViewModel.contentHeight)
.frame(maxWidth: .infinity)
.fixedSize(horizontal: false, vertical: true)
}
}
}

Related

SwiftUI: Is it possible to let the user scale an image chosen with PHpicker?

I have an image picker created with PHPicker, and I was wondering if it is possible to let the user scale the chosen image?
This is not the entire code, but just the code for the makeUIViewController which I think is what is needed to solve this problem. I can of course provide the rest of the code if necessary.
This is what I'm looking for
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 1
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
can use this one line after choose the image to fixed height and width of your image
Image(room.thumbnailImage)
.resizable()
.frame(width: 32.0, height: 32.0)
or here i am sharing my running work with you checkout function didFinishPicking and var body: some View
import SwiftUI
import PhotosUI
struct PhotoPickerDemo: View {
#State private var isPresented: Bool = false
#State var pickerResult: [UIImage] = []
var config: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images //videos, livePhotos...
config.selectionLimit = 0 //0 => any, set 1-2-3 for har limit
return config
}
var body: some View {
ScrollView {
LazyVStack {
Button("Present Picker") {
isPresented.toggle()
}.sheet(isPresented: $isPresented) {
PhotoPicker(configuration: self.config,
pickerResult: $pickerResult,
isPresented: $isPresented)
}
ForEach(pickerResult, id: \.self) { image in
Image.init(uiImage: image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 250, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.parent.pickerResult.append(newImage as! UIImage)
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
struct photoPickerDemo_Previews: PreviewProvider {
static var previews: some View {
PhotoPickerDemo()
}
}
or if you want to crop via user interface like attach picture
Step 1
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/marshallino16/ImageCropper
Step 2
in your didFinishPicking method where you are receiving selected image pass it in this package using these lines
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(yourSelectedImageHere),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
edited use of ImageCropperView
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(newImage),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}

How to search a custom text from textfield in webkit swiftUI

I am building a small browser in swift ui i have build a struct to represent the wkwebview and i want to enter any text in a textfield and search on the internet using wkwebview and a google query i tried to reinstantiate the Webview(web: nil, req: URLRequest(url: URL(string: searchText)!)) but its not working for me? how can i achieve this i am pretty new to SwiftUI.Please help?
struct Webview : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func refresh(){
webview?.reload()
}
}
import SwiftUI
struct ContentView: View {
var webview = Webview(web: nil, req: URLRequest(url: URL(string: "https://www.apple.com")!))
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
}
)
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
webview
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
webview.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
webview.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
webview.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
The way that you have things set up right now (attempting to store a reference to a UIViewRepresentable) is going to lead to problems later on (like this, in fact).
I suggest you set up an interim object of some sort that can be used to store data and communicate imperatively to the WKWebView while still retaining the ability to lay things out declaratively. In my example, WebVewManager is that interim object that both the parent view and the UIViewRepresentable have access to:
class WebViewManager : ObservableObject {
var webview: WKWebView = WKWebView()
init() {
webview.load(URLRequest(url: URL(string: "https://apple.com")!))
}
func searchFor(searchText: String) {
if let searchTextNormalized = searchText.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "https://google.com/search?q=\(searchTextNormalized)") { self.loadRequest(request: URLRequest(url: url))
}
}
func loadRequest(request: URLRequest) {
webview.load(request)
}
func goBack(){
webview.goBack()
}
func goForward(){
webview.goForward()
}
func refresh(){
webview.reload()
}
}
struct Webview : UIViewRepresentable {
var manager : WebViewManager
init(manager: WebViewManager) {
self.manager = manager
}
func makeUIView(context: Context) -> WKWebView {
return manager.webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
#StateObject private var manager = WebViewManager()
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
manager.searchFor(searchText: searchText)
})
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
Webview(manager: manager)
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
manager.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
manager.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
manager.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
I'm doing some simple percent encoding on the search string -- you may need to do more testing to make sure that google always accepts the search query, but this looks like it's working to me.

LazyVGrid, List, LazyStacks don't release views from memory?

I'm playing around with the new photo picker in SwiftUI 2 and I made a simple app to show the imported images in a LazyVGrid but when scrolling down, if I imported around 150 images the app finish all the memory and it crashes (Terminated due to memory issue).
I tried the same with a LazyVStack and List but they have the same problem, I was expecting lazy items to release all the cells that goes off screen from the memory but it doesn't look like it's working.
Is this a bug or am I doing something wrong?
Here's my code:
import SwiftUI
struct Media: Identifiable {
var id = UUID()
var image: Image
}
struct ContentView: View {
#State var itemProviders: [NSItemProvider] = []
#State private var showingPhotoPicker: Bool = false
let columns = [
GridItem(.adaptive(minimum: 100, maximum: 100), spacing: 8)
]
#State var medias: [Media] = []
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(medias) { media in
media.image
.resizable()
.scaledToFill()
.frame(width: 100, height: 100, alignment: .center)
.clipped()
}
}
}
.navigationBarTitle("Images \(medias.count)")
.navigationBarItems(leading: Button(action: {
loadImages()
}, label: {
Text("Import \(itemProviders.count) images")
}), trailing: Button(action: {
showingPhotoPicker.toggle()
}, label: {
Image(systemName: "photo.on.rectangle.angled")
}))
.sheet(isPresented: $showingPhotoPicker) {
MultiPHPickerView(itemProviders: $itemProviders)
}
}
}
func loadImages() {
for item in itemProviders {
if item.canLoadObject(ofClass: UIImage.self) {
item.loadObject(ofClass: UIImage.self) { image, error in
DispatchQueue.main.async {
guard let image = image as? UIImage else {
return
}
medias.append(Media(image: Image(uiImage: image)))
}
}
}
}
}
}
And the PhotoPickerView:
import SwiftUI
import PhotosUI
struct MultiPHPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) private var presentationMode
#Binding var itemProviders: [NSItemProvider]
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 0
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController( _ uiViewController: PHPickerViewController, context: Context) {}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
#Environment(\.presentationMode) private var presentationMode
var parent: MultiPHPickerView
init( _ parent: MultiPHPickerView ) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss( animated: true )
self.parent.itemProviders = results.map(\.itemProvider)
}
}
}

Dismiss WKWebView in SwiftUI

I'm new to Swift and building an application using SwiftUI.
I'm displaying a WKWebView using code that's similar to what I've written below.
struct SomeView: View {
#State var showWebView = false
var body: some View {
ZStack {
Button(action: {
self.showWebView.toggle()
}) {
Text("Go To WebView")
.padding()
.foregroundColor(.black)
.font(.title)
}
.sheet(isPresented: $showWebView, content: {
WebView(url: "https://www.apple.com/")
})
}
}
}
struct WebView: UIViewRepresentable {
var url: String
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
return webView
}
func updateUIView(_ uiView: WebView.UIViewType, context: UIViewRepresentableContext<WebView>) {
let urlRequest = URLRequest.init(url: URL.init(string: self.url)!)
uiView.navigationDelegate = context.coordinator
uiView.load(urlRequest)
}
}
class Coordinator: NSObject, WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url?.absoluteString {
if url.contains("apple") {
let someViewModel = SomeViewModel()
someViewModel.processRequest(url: url)
}
}
decisionHandler(.allow)
}
}
The code works as expected. I'm able to display a WKWebView showing the desired website. However, I'm struggling to figure out how to dismiss the WKWebView and navigate to another page once I've invoked someViewModel.processRequest(). Could you please assist?
I would do the whole WebView thing a bit different, which should (as far as I understand your problem) solve your situation:
import SwiftUI
import SafariServices
struct SomeView: View {
#State var showWebView = false
var body: some View {
ZStack {
Button(action: {
self.showWebView.toggle()
}) {
Text("Go To WebView")
.padding()
.foregroundColor(.black)
.font(.title)
}
.sheet(isPresented: $showWebView, content: {
WebView(url: URL(string: "https://www.apple.com/")!) })
}
}
}
struct WebView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<WebView>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<WebView>) {
}
}

Why does my WKWebView not show up in a swiftUI view?

I got the following swiftUI code:
import SwiftUI
import WebKit
struct WebView: NSViewRepresentable {
func makeNSView(context: Context) -> WKWebView {
let view: WKWebView = WKWebView()
guard let url: URL = URL(string: "https://google.com") else { return view }
let request: URLRequest = URLRequest(url: url)
view.load(request)
return view
}
func updateNSView(_ view: WKWebView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
WebView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
The problem is that it just shows an empty view. How do I fix that?
You can use below code and it uses NSViewRepresentable
struct WebView: NSViewRepresentable {
let view: WKWebView = WKWebView()
var request: URLRequest {
get{
let url: URL = URL(string: "https://google.com")!
let request: URLRequest = URLRequest(url: url)
return request
}
}
func makeNSView(context: Context) -> WKWebView {
view.load(request)
return view
}
func updateNSView(_ view: WKWebView, context: Context) {
view.load(request)
}
}
struct ContentView: View {
var body: some View {
GeometryReader { g in
ScrollView {
WebView()
.frame(height: g.size.height)
}.frame(height: g.size.height)
}
}
}
If it still not loads then please go to capabilities and check incoming and out going connections.