Avoid automatic video comercial when I launch a webPage using webkit - webkit

recently I created a News App for my personal News. I am using 2 Viewcontrollers one of them to show the list of news with the photo and the other to show the detail of the new in a detailViewController using webKit.
my problem is that every time a hit a news on the tableview and go to the detailViewController an ad video for the source page is show automatically and it's very annoying, some times a need to hit the close button more than 2 times to close it.
My question is if there any function to avoid those videos?
this is the code:
import UIKit
import WebKit
class DetailViewController: UIViewController {
var articleUrl:String?
#IBOutlet weak var spinner: UIActivityIndicatorView!
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
//check that there is url
if articleUrl != nil {
//create url objet
let urlObject = URL(string: articleUrl!)
guard urlObject != nil else {
return
}
//create the urlRequest
let request = URLRequest(url: urlObject!)
//start spinner
spinner.alpha = 1
spinner.startAnimating()
webView.configuration.allowsInlineMediaPlayback = true
webView.configuration.mediaTypesRequiringUserActionForPlayback = .video
webView.pauseAllMediaPlayback()
webView.load(request)
}
}
}
extension DetailViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
spinner.stopAnimating()
spinner.alpha = 0
}
}

Related

SwiftUI determine initial view

I'm trying to figure out the navigation in the beginning of an app I'm making in the SwiftUI lifecycle. Depending on settings determined at launch, the user may start in one of three views. If the user opens the app from a push notification, they should see the video view (WrappedVideoViewController). Otherwise, if they are logged in already, they should see the Home view, or if not, the Login view. I'm using ContentView as the parent:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var notifService: NotificationService
#EnvironmentObject var authState: AuthenticationState
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ObservedObject var viewRouter: ViewRouter
init() {
self.viewRouter = ViewRouter.shared
}
var body: some View {
print("in ContentView body")
switch viewRouter.currentScreen {
case .home:
print("home")
return AnyView(Home(innerGradientColor: Color.homeActiveInner, outerGradientColor: Color.homeActiveOuter))
case .login:
print("login")
return
AnyView(Login()
.environmentObject(AuthenticationState.shared))
case .video:
print("video")
return AnyView(WrappedVideoViewController())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AuthenticationState.shared)
.environmentObject(NotificationService())
}
}
My ViewRouter class is an ObservableObject meant to control which screen is shown. Currently, I have this:
//
// NavigationState.swift
// BumpCall-SwiftUI
//
// Created by Ben on 1/22/21.
//
import Foundation
import SwiftUI
enum Screen {
case home
case video
case login
}
class ViewRouter: ObservableObject {
var authState: AuthenticationState
var notifService: NotificationService
static var shared = ViewRouter(authState: AuthenticationState.shared, notifService: NotificationService.shared)
init(authState: AuthenticationState, notifService: NotificationService) {
self.authState = authState
self.notifService = notifService
if notifService.dumbData != nil {
currentScreen = .video
} else if (notifService.dumbData == nil && authState.isLoggedIn()) {
currentScreen = .home
} else {
currentScreen = .login
}
}
#Published var currentScreen: Screen
}
It's tricky because the ViewRouter is dependent on two other ObservableObjects, which I'm trying to handle here with the init(). It appears that the AuthenticationState class is working fine because upon opening the app, it does send you to the home screen if you are logged in. However, opening from a notification also sends me to the home screen. NotificationService changes the ViewRouter singleton in the didReceive response: method:
class NotificationService: NSObject, ObservableObject {
#Published var dumbData: UNNotificationResponse?
static var shared = NotificationService()
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
}
extension NotificationService: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("presenting notification")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.notifService.dumbData = response
print("Notification service didReceive response")
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { }
}
But the singleton must not update for ContentView to notice. This is super convoluted and it's been confusing me for days, any help would be much appreciated.
The problem is that ViewRouter init() will be called once. As you mentioned, when the user is logged in, currentScreen will be initialized to .home. However this variable will never be changed afterwards.
What you basically want, is to set currentScreen to .video inside the Notification delegate.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.currentScreen = .video //<< here set video
print("Notification service didReceive response")
completionHandler()
}

Creating Overlay / Polygon Mapkit Swift 5

I've tried to make an Overlay but nothing show on the map.
I want to make a 4 lines that make a shape like a square, that will show on the map (I added 4 CLLocationCoord).
What am I doing wrong?
Should I add some code?
I tried to add mapView.delegate = self but I don't know why it is doesn't work.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
let regionInMeters: Double = 1000
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
//calling the method
addBoundry()
}
func addBoundry(){ //creation of a polygon
var points = [CLLocationCoordinate2DMake(52.284428, 20.989394),
CLLocationCoordinate2DMake(52.224534, 21.044326),
CLLocationCoordinate2DMake(52.209182, 20.948024),
CLLocationCoordinate2DMake(52.247143, 20.918842),]
let polygon = MKPolygon(coordinates: &points, count: points.count)
mapView.addOverlay(polygon)
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = .magenta
return polygonView
}
return MKOverlayRenderer()
}
func setupLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation () {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
// Show alert letting the user know they have to turn this on.
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
// Show alert instructing them how to turn on perm
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
break
case .restricted:
// Show an alert letting them know what's up
break
case .authorizedAlways:
break
}
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {return}
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion.init(center: center, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
}
mapView.delegate = self
works when your class implements MKMapViewDelegate.
something like
class ViewController: UIViewController, MKMapViewDelegate {

UIMenuItem #selector method crash in wkwebview

UIMenuItem selector method crashes in iOS 11 beta SDK.
-[WKContentView highlightText]: unrecognized selector sent to instance 0x7f85df8f3200
Method Definition:
func highlightText()
{
//
}
I try to add UIMenuItem in WKWebView,
let menuItemHighlight = UIMenuItem.init(title: "Highlight", action: #selector(ContentWebkitView.highlightText))
UIMenuController.shared.menuItems = [menuItemHighlight]
I was also getting this error when I was overriding canPerformAction and checking for my custom selector. In my case I wanted to remove all menu items except for my custom one and the following made this work for me.
class ViewController: UIViewController {
#IBOutlet weak var webView: MyWebView!
override func viewDidLoad() {
super.viewDidLoad()
loadWebView()
setupCustomMenu()
}
func loadWebView() {
let url = URL(string: "http://www.google.com")
let request = URLRequest(url: url!)
webView.load(request)
}
func setupCustomMenu() {
let customMenuItem = UIMenuItem(title: "Foo", action:
#selector(ViewController.customMenuTapped))
UIMenuController.shared.menuItems = [customMenuItem]
UIMenuController.shared.update()
}
#objc func customMenuTapped() {
let yay = "🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪"
let alertView = UIAlertController(title: "Yay!!", message: yay, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "cool", style: .default, handler: nil))
present(alertView, animated: true, completion: nil)
}
}
class MyWebView: WKWebView {
// turn off all other menu items
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
OK, we finally made it work for Swift 4:
In your WKWebView subclass, add the following property and method:
// MARK: - Swizzling to avoid responder chain crash
var wkContentView: UIView? {
return self.subviewWithClassName("WKContentView")
}
private func swizzleResponderChainAction() {
wkContentView?.swizzlePerformAction()
}
Then, add an extension to UIView (I put it in the same file as my WKWebView subclass, you can make it fileprivate if you'd like)
// MARK: - Extension used for the swizzling part linked to wkContentView
extension UIView {
/// Find a subview corresponding to the className parameter, recursively.
func subviewWithClassName(_ className: String) -> UIView? {
if NSStringFromClass(type(of: self)) == className {
return self
} else {
for subview in subviews {
return subview.subviewWithClassName(className)
}
}
return nil
}
func swizzlePerformAction() {
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
private func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) {
if let currentMethod = self.instanceMethod(for: currentSelector),
let newMethod = self.instanceMethod(for:newSelector) {
let newImplementation = method_getImplementation(newMethod)
method_setImplementation(currentMethod, newImplementation)
} else {
print("Could not find originalSelector")
}
}
private func instanceMethod(for selector: Selector) -> Method? {
let classType = type(of: self)
return class_getInstanceMethod(classType, selector)
}
#objc private func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
Now the UIMenuItem works as expected:
But honestly, this really feels like a hack, and I would love Apple to fix this issue :-/
Thanks for Stephan Heilner for his answer: https://stackoverflow.com/a/42985441/4670400

Why can't I use write(toFile: ) property on a string?

I'm following a tutorial that was written in some earlier version of Swift, that teaches me how to read/write a .txt file in Swift3. Xcode has been doing a good job so far of letting me know when I'm using old syntax, and changing it for me to the latest syntax. However, I'm coming across something that works in an older version of Swift, but not the current one.
class ViewController: UIViewController {
// MARK: Properties
#IBOutlet weak var monthToEditTextField: UITextField!
#IBOutlet weak var bedTimeTextField: UITextField!
#IBOutlet weak var wakeTimeTextField: UITextField!
#IBOutlet weak var theLabel: UILabel!
#IBAction func saveButton(_ sender: UIButton)
{
var theMonth = monthToEditTextField.text
var bedTime = bedTimeTextField.text
var wakeTime = wakeTimeTextField.text
var stringForTXTFile = "The user's info is: \(theMonth), \(bedTime), \(wakeTime)"
let fileManager = FileManager.default
if (!fileManager.fileExists(atPath: filePath))
{
var writeError: NSError?
let fileToBeWritten = stringForTXTFile.write(toFile: // This is where the problem is
}
}
When I type
stringForTXTFile.write
I get this error box
What do I need to do in order to use the "write" property?
Write to file doesn't return Bool anymore in Swift3. It throws, so just delete let fileToBeWritten = and use do try catch error handling. Any code that needs to be run if the operation was successful needs to be placed below that inside the do try curly brackets. You can also use guard to unwrap your textfield optional strings. Try like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var monthToEditTextField: UITextField!
#IBOutlet weak var bedTimeTextField: UITextField!
#IBOutlet weak var wakeTimeTextField: UITextField!
#IBOutlet weak var theLabel: UILabel!
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("textFile.txt")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func save(_ sender: UIButton) {
guard
let theMonth = monthToEditTextField.text,
let bedTime = bedTimeTextField.text,
let wakeTime = wakeTimeTextField.text
else { return }
let stringForTXTFile = "The user's info is: \(theMonth), \(bedTime), \(wakeTime)"
do {
try stringForTXTFile.write(toFile: fileURL.path, atomically: true, encoding: .utf8)
// place code to be executed here if write was successful
} catch {
print(error.localizedDescription)
}
}
#IBAction func load(_ sender: UIButton) {
do {
theLabel.text = try String(contentsOf: fileURL)
} catch {
print(error.localizedDescription)
}
}
}

How to pass variables from one View Controller to another in WatchOS 2 & Swift

I am having a lot of problems trying to get a couple of variables from one View Controller to the next. How can I do it properly?
Here's my code below. This is the view controller where I want to be able to send the variables RedScoreW and BlueScoreW to the next window. I am asking on HOW TO DO THIS using SWIFT language and specially for WATCHOS apps.
class InterfaceController2: WKInterfaceController {
var RedScoreW = 0
var BlueScoreW = 0
#IBOutlet var WatchRedScoreLabel: WKInterfaceLabel!
#IBOutlet var WatchBlueScoreLabel: WKInterfaceLabel!
#IBAction func RedScorePlus() {
if RedScoreW == 999 {
RedScoreW = 0
WatchRedScoreLabel.setText("0")
}else {
RedScoreW += 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func RedScoreMinus() {
if RedScoreW == 0 {
RedScoreW = 999
WatchRedScoreLabel.setText("999")
}
else {
RedScoreW -= 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func BlueScorePlus() {
if BlueScoreW == 999 {
BlueScoreW = 0
WatchBlueScoreLabel.setText("0")
} else{
BlueScoreW += 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
#IBAction func BlueScoreMinus() {
if BlueScoreW == 0 {
BlueScoreW = 999
WatchBlueScoreLabel.setText("999")
}
else {
BlueScoreW -= 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
WatchRedScoreLabel.setText(String(RedScoreW))
WatchBlueScoreLabel.setText(String(BlueScoreW))
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
And this is the Destination View Controller where I want to be able to use RedScoreW and BlueScoreW variables.
class InterfaceController3: WKInterfaceController {
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
* EDIT *
I am trying to do it this way, this is the code where I send it, check:
#IBAction func FinishButtonPushVariables() {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
pushControllerWithName("LastScreen", context: arrayofScores)
}
And this is where I receive it... and it doesn't work. LOL
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalarrayofScores = context as? InterfaceController2
finalBlueScoreLabel.setText(String(finalarrayofScores!.arrayofScores[1]))
finalRedScoreLabel.setText(String(finalarrayofScores!.arrayofScores[0]))
// Configure interface objects here.
}
In iOS apps, we use prepareForSegue to do this. On watchOS apps, we use contextForSegueWithIdentifier to pass a context from one interfaceController to another.
Here is a link to the class reference that will detail more about this. But here are the basics:
There are two different methods that can be used. One is for going from one interface controller to another:
func contextForSegueWithIdentifier(_ segueIdentifier: String) -> AnyObject?
The other is for going from a one interface controller to another when a row in a table is tapped:
func contextForSegueWithIdentifier(_ segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex rowIndex: Int) -> AnyObject?
So one of these two methods will go in the interfaceController that is sending the context, and you will receive that context in the awakeWithContext method of the receiving interfaceController.
Here is a link to a tutorial that will show an application of this process.
EDIT
Here is a specific solution to your problem.
In the interface controller where you send it, put this code:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
return arrayOfScores
}
Then in your destination interface controller, put this code:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalArrayOfScores = context as? [Int]
if let f = finalArrayOfScores {
finalBlueScoreLabel.setText(String(f[1]))
finalRedScoreLabel.setText(String(f[0]))
}
}
You need to set up variables to hold your variable first.
class YourSecondViewController: UIViewController {
var yourVariable:Double?
}
Then have your button trigger your custom segue. Use your variable as the argument for sender.
class YourFirstViewController: UIViewController {
#IBAction func buttonTapped(sender: AnyObject) {
self.performSegueWithIdentifier("segue", sender: yourVariable)
}
}
Then pass the sender data by overriding the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier = "segue") {
let secondViewController = segue.destinationViewController as YourSecondViewController
let yourVariable = sender as Double
secondViewController.duration = yourVariable
}
}
I guess your problem is that you are passing an array to the context and you cast it as WKIntefaceController.
Try replacing this line
let finalarrayofScores = context as? InterfaceController2
by
let finalarrayofScores = context as? [Int]