When I open a safari view controller and then return to my app (when "Done" is pressed), my app renders a blank/white screen instead of the content for that view.
The code below is the code I have tried using in an empty view - the issue happens no matter where I try in my app.
import UIKit
import SafariServices
class SavedViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let link = URL(string: "https://google.com"){
let myrequest = SFSafariViewController(url: link)
myrequest.delegate = self
present(myrequest, animated:true)
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated:true, completion: nil)
}
}
The website loads fine, its when I return to my app that the blank screen appears. Am I doing something wrong?
It works fine.
You just created new UIViewController this way, UIViewController by default have black background. So when you press Done you just come back from SafariViewController to you SavedViewController (UIViewController).
Probably You are looking for UIWebView solution https://developer.apple.com/documentation/uikit/uiwebview.
If u want only display SafariViewController do it as function from your ViewController, You don't need to create new File with UIViewController class to do it.
import UIKit
import SafariServices
class SavedViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .gray
setupViews()
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated:true, completion: nil)
}
private func setupViews(){
view.addSubview(openSafariButton)
openSafariButton.addTarget(self, action: #selector(handleOpenSafariButtonTap), for: .touchUpInside)
openSafariButton.frame = CGRect(x: 0, y: 100, width: view.frame.width, height: 60)
}
#objc func handleOpenSafariButtonTap(){
if let link = URL(string: "https://google.com"){
let myrequest = SFSafariViewController(url: link)
myrequest.delegate = self
present(myrequest, animated:true)
}
}
let openSafariButton: UIButton = {
let button = UIButton()
button.setTitle("openSafari", for: .normal)
button.setTitleColor(.black, for: .normal)
button.backgroundColor = .red
return button
}()
}
That is what i mean.
You can add function:
private func openSafariLink(link: String){
if let link = URL(string: link){
let myrequest = SFSafariViewController(url: link)
present(myrequest, animated:true)
}
}
And call it from any place like that:
openSafariLink(link: "https://google.com")
This way fits more for Your solution.
Related
Eventually, I want to incorporate SKStoreProductViewController into a project I'm working on so to start I thought I'd recreate what was posted here. The only thing is, when I click on my button in the simulator (xcode 14.2) no modal is appearing. Could it be because I need to run this on a real device or maybe because storeViewController.loadProduct is 'silently failing'? Any and all help is much appreciated!
My dummy app:
//
// ViewController.swift
// SKStoreProductViewController Demo
//
import UIKit
import StoreKit
class ViewController: UIViewController {
let testFlightAppURL = URL(string: "https://apps.apple.com/us/app/testflight/id899247664")!
let testFlightProductID = 899247664
private let button: UIButton = {
let button = UIButton()
button.backgroundColor = .darkGray
button.setTitle("Open App Store", for: .normal)
return button
}()
override func viewDidLoad() {
view.addSubview(button)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.frame = CGRect(x: 30,
y: view.frame.height-150-view.safeAreaInsets.bottom,
width: view.frame.size.width-60,
height: 55)
}
#objc func didTapButton() {
openAppStore()
}
func openAppStore() {
// 2. Create an SKStoreProductViewController instance and set its delegate
let storeViewController = SKStoreProductViewController()
storeViewController.delegate = self
// 3. Indicate a specific product by passing its iTunes item identifier
let parameters = [SKStoreProductParameterITunesItemIdentifier: testFlightProductID]
storeViewController.loadProduct(withParameters: parameters) { _, error in
if error != nil {
// In case there is an issue loading the product, open the URL directly
UIApplication.shared.open(self.testFlightAppURL)
} else {
self.present(storeViewController, animated: true)
}
}
}
}
// MARK: - SKStoreProductViewControllerDelegate
// 4. A simple implementation of SKStoreProductViewController.
// Delegate dismisses the view controller when the user completes the purchase.
extension ViewController: SKStoreProductViewControllerDelegate {
func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
viewController.dismiss(animated: true)
}
}
Testing on a real device resolved the issue for me.
I have an app reader like Apple Books. And I have a problem. I have NavigationViewController next FirstViewController with book button, SecondViewController it is a reader and TableViewController it is settings for text size. But if I open book in FirstViewController go to SecondViewController and in navigation bar tab aA button and open TableViewController all works fine. But after if I want back to FirstViewController I not close TableViewController and do swipe from left to right my app stops. How to fix it?
SecondViewController:
#IBOutlet weak var textSettings: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupGestures()
}
// MARK: - Text Settings View
private func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapGesture.numberOfTapsRequired = 1
textSettings.addGestureRecognizer(tapGesture)
}
#objc private func tapped(){
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = self.textSettings
popOverVC?.sourceRect = CGRect(x: self.textSettings.bounds.midX, y: self.textSettings.bounds.maxY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
extension SecondViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Other controllers have no special code. Video illustrating the problem - https://drive.google.com/file/d/1HUV26H4VFoKglh8FbkNP0y2Y3rYl4Q_f/view?usp=sharing
I am presenting some buttons with big images. When the button is clicked, it is removed from the superview successfully. The button is also being de initialised. Still the image is kept in memory, and if several buttons are presented afterwards, the app crashes due to memory constraints. The button gets removed from the superview, but the image is not released.
Test Code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let button = UIButton(frame: view.bounds)
button.setBackgroundImage(getImage(), for: .normal)
view.addSubview(button)
button.addAction(UIAction { [weak button] _ in
button?.removeFromSuperview()
}, for: .touchUpInside)
}
func getImage() -> UIImage? {
if let path = Bundle.main.path(forResource: "super", ofType: "png") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
I don't see any retain cycles there, for the image to be kept in memory.
And IMPORTANT: If I call button?.removeFromSuperview() from any other button or method, the image gets successfully released:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let buttonWithImage = UIButton(frame: view.bounds)
buttonWithImage.setBackgroundImage(getImage(), for: .normal)
view.addSubview(buttonWithImage)
let dismissingButton = UIButton(frame: view.bounds)
view.addSubview(dismissingButton)
dismissingButton.addAction(UIAction { [weak buttonWithImage] _ in
buttonWithImage?.removeFromSuperview()
}, for: .touchUpInside)
}
func getImage() -> UIImage? {
if let path = Bundle.main.path(forResource: "super", ofType: "png") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
I am out of ideas for this issue. Any help is appreciated. Thanks.
Check if this works for you:
button.addAction(UIAction(handler: { action in
let button = action.sender as! UIButton
button.removeFromSuperview()
}), for: .touchUpInside)
I'm developing a tinderlike swipe app for babynames. I've created a method that, when you click a button besides one of your favorite names, it pops up a window (A UIViewController with a WKWebView) that shows a google search with the meaning of this particular name.
The problem is that with some names, the app crashes because the URL returns nil. I don't understand what's happening, because other names are working just fine. I've found out it happens with Scandinavian names like "OddVeig" and "OddLaug" .
Below is my UIViewController class that pops up with the search result (I've put all the initializing code in the layoutSubViews(), because I had some trouble in iOS 12, where the WKWebView wouldn't resize properly if I put it in the viewDidLoad() ) :
import UIKit
import WebKit
class WebViewController: UIViewController, WKNavigationDelegate {
#IBOutlet weak var webContainer: UIView!
var webView : WKWebView!
var query : String?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
webView = WKWebView()
webView.navigationDelegate = self
webContainer.addSubview(webView)
self.view.alpha = 0.9
webView.frame = self.view.frame
webView.layer.position.y += 20
let slideDownImage = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 30))
slideDownImage.setImage(UIImage(named: "slideIconWhite"), for: .normal)
slideDownImage.imageView?.contentMode = .scaleAspectFit
if let n = query {
let url = URL(string: "https://www.google.com/search?q=\(n)")! // Causing error: Found nil
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
webContainer.addSubview(slideDownImage)
slideDownImage.frame = CGRect(x: 0, y: 0, width: self.webView.frame.size.width, height: 20)
slideDownImage.backgroundColor = .gray
slideDownImage.alpha = 0.7
slideDownImage.addTarget(self, action: #selector(slideDown), for: .touchUpInside)
}
#objc func slideDown() {
self.dismiss(animated: true, completion: nil)
}
}
I use a segue in the FavoritesViewController class like this :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "favoritesToWeb" {
let destination = segue.destination as! WebViewController
destination.query = "\(searchName)+\(NSLocalizedString("meaning", comment: "meaning"))"
}
}
Hopefully someone can tell me what I'm doing wrong.
Thanks in advance.
Patrick
query variable need to be percentage encoded, I guess this must be crashing while you have whitespace characters in query string. use addingPercentEncoding for adding percentage encoding to query string.
Reference to API:
https://developer.apple.com/documentation/foundation/nsstring/1411946-addingpercentencoding
Related Question:
Swift. URL returning nil
How can I set a rule such that when my user clicks any web link within my app, it opens in the in-app browser instead of Safari?
Context: I'm building an app that has several places where links are either embedded by me or are loaded through various user interactions, e.g. a link leading to a page that houses another link. I want to ensure that the user seldom leaves my app and hence want to open all external web links in an in-app browser that has already been developed
Target Build: iOS 11.
Environment/ Language: Swift 4
Simple solution using SFSafariViewController which available in a separate package
import SafariServices
Swift 4, iOS 9.0+
let url = URL(string: "your URL here")
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
If you need more control - use Configuration parameter (Swift 4, iOS 11.0+):
Example:
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = true
let url = URL(string: "your URL here")
let vc = SFSafariViewController(url: url, configuration: config)
present(vc, animated: true)
...it opens in the in-app browser instead of Safari?
In that case, you would need your own WebView. Here's a quick snippet of a simple webView inside a controller:
import UIKit
import WebKit
/// The controller for handling webviews.
class WebViewController: UIViewController {
// MARK: - Properties
internal lazy var button_Close: UIButton = {
let button = UIButton(type: .custom)
button.setImage(.close, for: .normal)
button.imageEdgeInsets = UIEdgeInsets.init(top: 0, left: -30, bottom: 0, right: 0)
button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside)
return button
}()
public var urlString: String! {
didSet {
if let url = URL(string: urlString) {
let urlRequest = URLRequest(url:url)
self.webView.load(urlRequest)
}
}
}
private lazy var webView: WKWebView = {
let webView = WKWebView()
webView.navigationDelegate = self
return webView
}()
// MARK: - Functions
// MARK: Overrides
override func viewDidLoad() {
super.viewDidLoad()
let barButton = UIBarButtonItem(customView: self.button_Close)
self.button_Close.frame = CGRect(x: 0, y: 0, width: 55.0, height: 44.0)
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fixedSpace, target: nil, action: nil)
if #available(iOS 11.0, *) {
negativeSpacer.width = -30
}
self.navigationItem.leftBarButtonItems = [negativeSpacer, barButton]
self.view.addSubview(self.webView)
self.webView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
#objc func back(_ sender: Any) {
self.dismiss()
}
}
extension WebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
// Show here a HUD or any loader
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Dismiss your HUD
}
}
and presenting such webViewController, like so (passing a URLString):
let webViewVC = WebViewController()
webViewVC.urlString = "https://www.glennvon.com/"
let navCon = UINavigationController(rootViewController: webViewVC)
self.navigationController?.present(navCon, animated: true, completion: nil)
If you're using storyboard, then simply drag a webview in your controller and setup the delegate. This should help you out :)