Any way to get a gif as a background with swiftUI? - swift

I am trying to get a gif to be a background layer of a view but swiftUI doesn't seem to allow that. I've looked through stack overflow and found it is possible to do so through UIKit, but I'm still new to swift in general and not that comfortable with UIKit.
If there is any way to do so with swiftUI, would love for some input.
If a gif isn't possible, maybe an automatic video in the background?
Thanks

I've used a WKWebView to display a gif. The resulting view can be set anywhere. To set it as a background, you'll probably want to resize the contents of the WKWebView according to the contents of the superview.
import SwiftUI
import WebKit
struct HTMLRenderingWebView: UIViewRepresentable {
#Binding var htmlString: String
#Binding var baseURL: URL?
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if self.htmlString != context.coordinator.lastLoadedHTML {
print("Updating HTML")
context.coordinator.lastLoadedHTML = self.htmlString
uiView.loadHTMLString(self.htmlString, baseURL: self.baseURL)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var parent: HTMLRenderingWebView
var lastLoadedHTML = ""
init(_ parent: HTMLRenderingWebView) {
self.parent = parent
}
}
}
struct HTMLRenderingWebViewExample: View {
#State var htmlString = ""
var body: some View {
VStack {
HTMLRenderingWebView(htmlString: self.$htmlString, baseURL: .constant(nil))
.padding(30).background(Color.gray)
Button("Click this button") {
self.htmlString = "<meta name=\"viewport\" content=\"initial-scale=1.0\" />" +
(self.assetAsString() ?? "image loading failed")
}
}.navigationBarTitle("Example HTML Rendering")
}
func assetAsString() -> String? {
let asset = NSDataAsset(name: "User_OhSqueezy_on_commons_wikimedia_org_Parallax_scrolling_example_scene")
if let data = asset?.data {
let base64string = data.base64EncodedString()
let format = "gif"
return "<img src='data:image/\(format);base64," + base64string + "' height=240 width=360>"
} else {
return nil
}
}
}
I got my animated gif from Wikimedia Commons and dragged it into the Assets.xcassets in Xcode.
Result:

Related

How do I inject CSS/JS in a WKWebView using SwiftUI?

I'm new to SwiftUI and trying to inject some custom CSS/JS into a page loaded with WKWebView:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string: "https://example.com")!)
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
webView.load(request)
webView.configuration.userContentController.addUserScript(WKUserScript( source: "alert('debug')", injectionTime: .atDocumentEnd, forMainFrameOnly: true))
}
}
Which is load like this:
struct ContentView: View {
var body: some View {
WebView()
}
}
Sadly, the code doesn't seem to actually inject anything. I've tried running it before webView.load as well. Having been googling quite a bit, I only see examples done in UIKit and unfortunately, I'm too inexperienced to wrap UIKit in a way that I can use with SwiftUI.
Any guidance would be greatly appreciated.
First of all try to avoid including business code in your views whenever you can. You may use two functions in the Webkit API if you want to include/inject JS to the webview content: EvaluateJS and AddUserScript. You may use "AddUserScript" before the "load" starts. Also please not that "alert" function in JS, would not work in current Mobile Safari. You should have see the text colors to appear in blue with the script below.
Result:
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
VStack {
CustomWebview()
}
.padding()
}
}
struct SwiftUIWebView: UIViewRepresentable {
typealias UIViewType = WKWebView
let webView: WKWebView
func makeUIView(context: Context) -> WKWebView {
webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
final class SwiftUIWebViewModel: ObservableObject {
#Published var addressStr = "https://www.stackoverflow.com"
let webView: WKWebView
init() {
webView = WKWebView(frame: .zero)
loadUrl()
}
func loadUrl() {
guard let url = URL(string: addressStr) else {
return
}
webView.configuration.userContentController.addUserScript(WKUserScript( source: """
window.userscr ="hey this is prior injection";
""", injectionTime: .atDocumentStart, forMainFrameOnly: false))
webView.load(URLRequest(url: url))
// You will have the chance in 8 seconds to open Safari debugger if needed. PS: Also put a breakpoint to injectJS function.
DispatchQueue.main.asyncAfter(deadline: .now() + 8.0) {
self.injectJS()
}
}
func injectJS () {
webView.evaluateJavaScript("""
window.temp = "hey here!";
document.getElementById("content").style.color = "blue";
""")
}
}
struct CustomWebview: View {
#StateObject private var model = SwiftUIWebViewModel()
var body: some View {
VStack {
SwiftUIWebView(webView: model.webView)
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

WKWebview not loading from another struct

This is a web browser app for macOS. To keep things organized, I’m moving some bookmark buttons from ContentView to their own struct, but now the webview doesn’t load the site after clicking any of the buttons.
They only work if they’re stored inside ContentView and I'm not sure why.
struct bookmarkbuttons: View {
#State var webView: WebView?
var body: some View {
Button {
webView?.youtube()
}
...........
}
ContentView
#State var webView: WebView?
struct ContentView: View {
................
bookmarkbuttons()
webView
}
.onAppear {
webView = WebView(webModel: webModel) }
}
WebView
struct WebView: NSViewRepresentable {
#ObservedObject var webModel: WebStateModel
let wkWebview = WKWebView()
func makeNSView(context: Context) -> WKWebView {
if let theUrl = webModel.url {
let request = URLRequest(url: theUrl, cachePolicy: .returnCacheDataElseLoad)
wkWebview.load(request)
}
return wkWebview
}
}
func updateNSView(_ nsView: WKWebView, context: Context) {
if let theUrl = webModel.url {
let request = URLRequest(url: theUrl, cachePolicy: .returnCacheDataElseLoad)
nsView.uiDelegate = context.coordinator
nsView.load(request)
}
func youtube() {
wkWebview.load(URLRequest(url: (URL(string: "https://www.youtube.com")!)))
}
}

Easiest way to play a video from youtube in my app with swiftui?

I am trying to make a video player in my app (with swiftUI) that playes videos from youtube when the user creates a URL string. To practise I have seperated this into a new project to see that it works. But the screen is just black with a play button. Nothing happends when I press the play button or the screen.
This is my code:
import SwiftUI
import AVKit
struct ContentView: View {
let url = URL(string: "https://youtu.be/Wlf1T5nrO50")!
var body: some View {
VStack{
VideoPlayer(player: AVPlayer(url: url))
.scaledToFit()
}
}
I found another video with how to make embedded youtube videos but then you need to just copy the video ID and my user is not that advanced. I want the user to be able to just copy the URL.
Thankful for any help.
A better Solution I made
struct YoutubeVideoView: UIViewRepresentable {
var youtubeVideoID: String
func makeUIView(context: Context) -> WKWebView {
WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let path = "https://www.youtube.com/embed/\(youtubeVideoID)"
guard let url = URL(string: path) else { return }
uiView.scrollView.isScrollEnabled = false
uiView.load(.init(url: url))
}
}
A YouTube url is not a valid video URL. You have to either add youtube's library or play it using a WKWebView.
Also check out this package, it's awesome!
Using WKWebView:
struct LinkView: View {
var link: String
var body: some View {
WebView(url: URL(string: link.embed)!)
}
}
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
extension String {
var embed: String {
var strings = self.components(separatedBy: "/")
let videoId = strings.last ?? ""
strings.removeLast()
let embedURL = strings.joined(separator: "/") + "embed/\(videoId)"
return embedURL
}
}
Usage:
LinkView(link: "https://youtube.com/videoid")
Remember, this is a very basic WebView that does not handle errors and loading.

A link with swift webview application target _blank is not working

I'm a beginner, sorry I did webview with swift, but a link with target _blank doesn't work
I've created a UIViewRepresentable class for the WKWebView from UIKit. The UIViewRepresentable class can be used to create and manage views(UIView) from UIKit in SwiftUI. There are two views in the project,
ContentView lists the urls from string array. Selecting an url navigates to the Detail view.
The Detail view shows the web page for the selected url using WKWebView.
Hope this helps. Here's the code.
import SwiftUI
import UIKit
import WebKit
struct ContentView: View {
var urls: [String] = ["https://www.stackoverflow.com", "https://www.yahoo.com"]
#State private var hideStatusBar = false
var body: some View {
NavigationView {
List {
ForEach(urls, id: \.self) { url in
VStack {
NavigationLink(destination: DetailView(url: url)) {
Text(url)
}
}
}
}
.navigationBarTitle("Main")
}
}
}
struct DetailView: View {
var url: String = ""
var body: some View {
VStack {
Webview(url: url)
Spacer()
}
.navigationBarHidden(true)
}
}
struct Webview: UIViewRepresentable {
var url: String
typealias UIViewType = WKWebView
func makeUIView(context: UIViewRepresentableContext<Webview>) -> WKWebView {
let wkWebView = WKWebView()
guard let url = URL(string: self.url) else {
return wkWebView
}
let request = URLRequest(url: url)
wkWebView.load(request)
return wkWebView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<Webview>) {
}
}

LinkPresentation views not fully loading in SwiftUI

I'm using code I found from a great article here that demonstrates how to use the LinkPresentation framework in SwiftUI.
However I'm having a small problem that I can't find solution to - the link previews loads their metadata but don't refresh the view once fully loaded unless I do something which forces the view to refresh, like rotating the phone.
They load as much as this:
Then look like this after rotating:
I'd like the views to fully refresh once the metadata is loaded. I feel like I probably need to add some binding in somewhere but I don't know where. Can anyone help at all?
Here's the UIViewRepresentable
import SwiftUI
import LinkPresentation
struct URLPreview : UIViewRepresentable {
var previewURL:URL
func makeUIView(context: Context) -> LPLinkView {
LPLinkView(url: previewURL)
}
func updateUIView(_ view: LPLinkView, context: Context) {
// New instance for each update
let provider = LPMetadataProvider()
provider.startFetchingMetadata(for: previewURL) { (metadata, error) in
if let md = metadata {
DispatchQueue.main.async {
view.metadata = md
view.sizeToFit()
}
}
}
}
}
and here's how it's called:
struct Content: View {
var body: some View {
URLPreview(previewURL: URL(string: "www.apple.com")!)
}
}
Triggering a redraw is what you need. Not a fan of this, but you can try Binding a State CGSize and set frame to width/height.
struct URLPreview : UIViewRepresentable {
var previewURL:URL
//Add binding
#Binding var metaSize: CGSize
func makeUIView(context: Context) -> LPLinkView {
LPLinkView(url: previewURL)
}
func updateUIView(_ view: LPLinkView, context: Context) {
// New instance for each update
let provider = LPMetadataProvider()
provider.startFetchingMetadata(for: previewURL) { (metadata, error) in
if let md = metadata {
DispatchQueue.main.async {
view.metadata = md
view.sizeToFit()
//Set binding after resize
self.metaSize = view.frame.size
}
}
}
}
}
struct ContentView: View {
//can default original state
#State var metaSize: CGSize = CGSize()
var body: some View {
URLPreview(previewURL: URL(string: "www.apple.com")!, metaSize: $metaSize)
.frame(width: metaSize.width, height: metaSize.height)
}
}
UPDATE
NSPratik is right, the solution is not really viable for Lists. So an amended solution is actually just to use a simple Bool State to toggle the Views generated by a list:
struct ContentView: View {
//can default original state
#State var togglePreview = false
let urls: [String] = ["https://medium.com","https://apple.com","https://yahoo.com","https://stackoverflow.com"]
var body: some View {
List(urls, id: \.self) { url in
URLPreview(previewURL: URL(string: url)!, togglePreview: self.$togglePreview)
.aspectRatio(contentMode: .fit)
.padding()
}
}
}
struct URLPreview : UIViewRepresentable {
var previewURL:URL
//Add binding
#Binding var togglePreview: Bool
func makeUIView(context: Context) -> LPLinkView {
let view = LPLinkView(url: previewURL)
let provider = LPMetadataProvider()
provider.startFetchingMetadata(for: previewURL) { (metadata, error) in
if let md = metadata {
DispatchQueue.main.async {
view.metadata = md
view.sizeToFit()
self.togglePreview.toggle()
}
}
}
return view
}
func updateUIView(_ uiView: LPLinkView, context: UIViewRepresentableContext<URLPreview>) {
}
}
We simply use togglePreview as our trigger, pass it to a Binding var in the UIView, and then setup our List. Even if this triggers all the Views in the List, there won't be any animation to reflect the resize of fully loaded LinkViews.
Using LPLinkViews in a List causes huge memory leaks. Your best bet is to use a VStack embedded inside a ScrollView.
ScrollView {
VStack {
ForEach(links, id: \.self) { link in
if let url = URL(string: link) {
LinkRow(url: url)
}
}
}
.padding()
}
This will make the LPLinkViews resize themselves as they load.
I have done this in an app and it has significant improvement over using a List. However a little caveat, if the user stars scrolling up and down as soon as the view comes on screen while the previews are still loading, it might causes crashes at random. Unfortunately I haven't been able to find a solution for that yet. I think all these crashes happen because the LPMetadataProvider requires you to be called on the main thread and obviously that doesn't play well with smooth scrolling.