PDFView with PKCanvasView drawingGestureRecognizer on iOS14 - ios

Minimum code to add a PKCanvasView to PDFView. The PKCanvasView displays properly if the PKDrawing is set. However on iOS14, the drawingGestureRecognizer does not fire. Works on iOS13
import UIKit
import PDFKit
import PencilKit
class ViewController: UIViewController {
#IBOutlet var pdfView: PDFView!
var scrollView : UIScrollView!
var pkView : PKCanvasView!
var docView : UIView!
var drawing : PKDrawing!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupPDF()
setupPencil()
}
func setupPDF() {
let fileURL = Bundle.main.url(forResource: "test", withExtension: "pdf")!
let pdfDocument = PDFDocument(url: fileURL)
pdfView.document = pdfDocument
}
func setupPencil() {
for scroll in self.allSubViews(in: self.pdfView, ofType: UIScrollView.self) {
self.scrollView = scroll
break
}
for view in self.allSubViews(in: self.scrollView, ofType: UIView.self) {
if "\(view)".starts(with: "<PDFDocumentView: ") {
self.docView = view
break
}
}
self.pkView = PKCanvasView(frame: CGRect(origin: CGPoint.zero, size: self.docView.bounds.size))
self.pkView.isOpaque = false
self.pkView.backgroundColor = .clear
self.docView.addSubview(self.pkView)
self.pkView.tool = PKInkingTool(.pen, color: .black, width:0.4)
self.pkView.overrideUserInterfaceStyle = .light
if let _drawing = self.drawing {
self.pkView.drawing = _drawing
}
self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2
self.scrollView.addGestureRecognizer(self.pkView.drawingGestureRecognizer)
}
func allSubViews<T: UIView>(in view:UIView, ofType type: T.Type) -> [T] {
var all: [T] = []
func getSubview(view: UIView) {
if let aView = view as? T {
all.append(aView)
}
guard view.subviews.count > 0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: view)
return all
}
}
FYI, the pages are rendered dynamically and can be added and removed from the hierarchy. If you're using this code, you need to bringSubviewToFront when pages change.

Related

Data Sharing Between My App and App Extensions

I transfer data from the sharing extension to my main application with UserDefaults and open the application (goToApp()) after hitting the "post" button. However, the view of my app is not redrawn and the text remains the same "Share Extension Example". Here's how I'm trying to do it:
class ShareViewController: SLComposeServiceViewController {
private var textString: String?
override func isContentValid() -> Bool {
if let currentMessage = contentText {
self.textString = currentMessage
}
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didSelectPost() {
UserDefaults.standard.set(self.textString!, forKey: "text")
gotoApp()
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func gotoApp() {
guard let url = URL(string: "example://") else { return }
let selectorOpenURL = sel_registerName("openURL:")
var responder: UIResponder? = self
while responder != nil {
if responder?.responds(to: selectorOpenURL) == true {
responder?.perform(selectorOpenURL, with: url)
}
responder = responder?.next
}
}
}
And the project to which I am trying to transfer data:
class ViewController: UIViewController {
private let mainVStack = UIStackView()
private let backgroundView = UIImageView()
private let titleLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
configureMainStack()
configureTitleLabel()
}
}
// MARK: - UI Elements
private extension ViewController {
func configureMainStack() {
mainVStack.distribution = .fillProportionally
mainVStack.embed(asSubviewTo: view, inset: 40)
}
func configureTitleLabel() {
titleLabel.textAlignment = .center
titleLabel.textColor = .blue
if let text = UserDefaults.object(forKey: "text") as? String {
titleLabel.text = text
} else {
titleLabel.text = "Share Extension Example"
}
let titleContainerView = UIView()
titleLabel.embedIn(titleContainerView, hInset: 0, vInset: 100)
mainVStack.addArrangedSubview(titleContainerView)
}
}

How to change View while app is running in SwiftUI

struct AnimationView: UIViewRepresentable {
#Binding var cnt: Int
var imageView: UIImageView = UIImageView(image: effctPicker(cnt: 0))
func makeUIView(context: Self.Context) -> UIImageView {
return imageView
}
func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<AnimationView>) {
if self.cnt == 0{
self.imageView.image = effctPicker(cnt: 0)
}
else {
self.imageView.image = effctPicker(cnt: self.cnt)
}
}
}
struct ContentView: View {
#State var count:Int = 0
#State var audioPlayer: AVAudioPlayer!
#State var bg: String = "bg"
var body: some View {
Button(action:{
count += 1
switch count
{
case 1:
self.bg = "bg_2"
default:
count = 0
self.bg = "bg"
}
}, label: {
ZStack {
Image(bg)
.resizable()
AnimationView(cnt: $count)
}.onAppear {
let sound = Bundle.main.path(forResource: "freelove", ofType: "mp3")
self.audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound!))
self.audioPlayer.play()
}
}
)
}
}
var springImages = [UIImage(named: "eft1")!, UIImage(named: "eft2")!,UIImage(named: "eft3")!, UIImage(named: "eft4")!]
var rainImages = [UIImage(named: "raineft_1")!, UIImage(named: "raineft_2")!,UIImage(named: "raineft_3")!]
func effctPicker(cnt : Int) ->UIImage{
if cnt == 0{
return UIImage.animatedImage(with: springImages, duration: 0.4)!
}
else {
return UIImage.animatedImage(with: rainImages, duration: 0.4)!
}
}
First of all, I'm sorry that my english skill is not good. And this is my first question in stackoverflow
This is my animation view
I want when i press the button, binded "cnt" will change and updaateUIView will be called.
But it didn't worked. Should I add coordinator? If so, what should I do?

UIViewRepresentable wont update my ios chart dataset

I am trying to my data derived from an API into a line chart but I can't seem to get it to work. I am storing the data in an observable object so it takes a few seconds to get it so it won't show up on my graph but when I hardcode data it works I am certain that I am getting the data but it simply won't show up. thanks
struct HomeView: View {
#State var tabIndex:Int = 0
#ObservedObject var homeViewModel = HomeViewModel()
init() {
homeViewModel.getTimelineBy("US")
}
var body: some View {
VStack(alignment: .center) {
TimelineChartView(timelineDataSet: self.$homeViewModel.countryTimeline)
}.frame(height: 500.0)
}
}
struct TimelineChartView: UIViewRepresentable {
#Binding var timelineDataSet: [ChartDataEntry]
func updateUIView(_ uiView: LineChartView, context: UIViewRepresentableContext<TimelineChartView>) {
}
var lineChart = LineChartView()
func makeUIView(context: UIViewRepresentableContext<TimelineChartView>) -> LineChartView {
setUpChart()
return lineChart
}
func setUpChart() {
lineChart.noDataText = "No Data Available"
lineChart.rightAxis.enabled = false
lineChart.backgroundColor = .white
let dataSets = [getLineChartDataSet()]
let yAxis = lineChart.leftAxis
yAxis.labelFont = .boldSystemFont(ofSize: 13)
yAxis.setLabelCount(5, force: false)
yAxis.labelTextColor = .black
yAxis.axisLineColor = .black
yAxis.labelPosition = .outsideChart
lineChart.xAxis.labelPosition = .bottom
lineChart.xAxis.labelFont = .boldSystemFont(ofSize: 13)
lineChart.xAxis.labelTextColor = .black
lineChart.xAxis.axisLineColor = .systemBlue
lineChart.animate(xAxisDuration: 2.5)
lineChart.notifyDataSetChanged()
let data = LineChartData(dataSets: dataSets)
data.setValueFont(.systemFont(ofSize: 7, weight: .black))
lineChart.data = data
}
func getChartDataPoints(selectedTimelineData: [ChartDataEntry]) -> [ChartDataEntry] {
var dataPoints: [ChartDataEntry] = []
for eachTimeline in selectedTimelineData {
let entry = ChartDataEntry(x: eachTimeline.x, y: eachTimeline.y)
dataPoints.append(entry)
}
return dataPoints
}
func getLineChartDataSet() -> LineChartDataSet {
let test = getChartDataPoints(selectedTimelineData: timelineDataSet)
let set = LineChartDataSet(entries: test, label: "DataSet")
set.lineWidth = 4
set.drawCirclesEnabled = false
set.mode = .cubicBezier
set.fillAlpha = 0.9
set.drawFilledEnabled = true
set.highlightColor = .systemRed
return set
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
Let me share some of my codes working. When the binding object is changed the chart will show the data changed.
struct HomeView: View {
#State var barData: [String: Double] = [String: Double]()
var body: some View {
NavigationView {
List {
CustomeView([BarChartVM(arg: self.$barData, title: "BarChart")])
}
}
}
}
struct CustomeView<Page:View>: View {
var viewControllers: [UIHostingController<Page>]
init(_ views: [Page]) {
self.viewControllers = views.map { UIHostingController(rootView: $0) }
}
var body: some View {
CustomeViewController(controllers: viewControllers)
}
}
struct CustomeViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[controllers[0]], direction: .forward, animated: true)
}
}
struct BarChartVM: UIViewRepresentable {
#Binding var arg: [String: Double]
var title: String = ""
let chart = BarChartView()
func makeUIView(context: UIViewRepresentableContext<BarChartVM>) -> BarChartView {
setUpChart()
return chart
}
func updateUIView(_ uiView: BarChartView, context: UIViewRepresentableContext<BarChartVM>) {
updateChartData()
}
func setUpChart() {
chart.noDataText = "No data available"
let pointArray = arg.sorted(by: <).map { $0.key }
}
func updateChartData() {
var entries = [BarChartDataEntry]()
let valueArray = arg.sorted(by: <).map { $1 }
for i in 0..<valueArray.count {
let entry = BarChartDataEntry(x: Double(i), yValues: [valueArray[i]])
entries.append( entry)
}
let set = BarChartDataSet(entries: entries, label: title)
set.colors = ChartColorTemplates.material()
let data = BarChartData(dataSet: set)
chart.data = data
}
}

Crash on UICollectionViewCell with JWVideoView - Swift

A ViewController has a UICollectionView. One of the cells contains JWVideoView. The app is frequently crashing on prepareForReuse in this cell.
There is no valuable info in the log. So I am having trouble figuring out the reason for the crash.
I've created a project example that demonstrates the crash. You can find it https://github.com/fuxlud/JWExample
If the link between the cell and the videoView is removed, the crash will not happen.
import UIKit
class VideoArticleElementCollectionViewCell: UICollectionViewCell {
// MARK: - Properties
public var imageURL: String? { didSet { videoView?.imageURL = imageURL } }
public var videoId: String? { didSet { videoView?.videoId = videoId } }
#IBOutlet private var videoView: JWVideoView?
// MARK: - Reuse
override func prepareForReuse() {
super.prepareForReuse() // Crashing here! (Thread 1: EXC_BAD_ACCESS (code=1, address=0x7e8))
videoView?.stopPlayingVideo()
}
deinit {
videoView?.stopPlayingVideo()
}
}
import UIKit
class JWVideoView: UIView, JWPlayerDelegate {
// MARK: Properties
public var imageURL: String?
public var videoId: String? { didSet { setupPlayer() } }
private var jwPlayer: JWPlayerController?
private let jwPlayerURL = "https://content.jwplatform.com/manifests/"
private var didPause = false
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
// MARK: - Setup
private func setup() {}
private func setupPlayer() {
guard let videoId = self.videoId else { return }
let playerURL = jwPlayerURL + videoId + ".m3u8"
let configuration: JWConfig = JWConfig(contentURL: playerURL)
configuration.controls = true
configuration.autostart = true
// configuration.premiumSkin = JWPremiumSkinGlow
configuration.image = imageURL
jwPlayer = JWPlayerController(config: configuration)
if let player = jwPlayer {
player.forceFullScreenOnLandscape = true
player.forceLandscapeOnFullScreen = true
player.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
player.view?.frame = bounds
player.delegate = self
player.volume = 0.0
if let view = player.view { addSubview(view) }
}
}
// MARK: - Orientation
private func enableAllOrientation(enable: Bool) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
// delegate.shouldEnableLandscape = enable
}
}
// MARK: API
public func stopPlayingVideo() {
enableAllOrientation(enable: false)
if jwPlayer != nil {
jwPlayer!.stop()
}
}
// MARK: - JWPlayerDelegate
internal func onFullscreen(_ status: Bool) {
if status == false {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
}
internal func onPlayAttempt() {
if jwPlayer != nil {
enableAllOrientation(enable: true)
}
}
internal func onPlay(_ oldValue: String) {
if didPause {
didPause = false
}
}
internal func onPause(_ oldValue: String) {
didPause = true
}
internal func onComplete() {
}
}
Based on your example project a saw the following issue inside your JWVideoView class: everytime you setting the videoId property it initiliaze the jwPlayer again, and also readds this view again to the stack.
1. Solution (remove the playerView and set the player to nil):
private func setupPlayer() {
jwPlayer?.view?.removeFromSuperview()
jwPlayer = nil
guard let videoId = self.videoId else { return }
let playerURL = jwPlayerURL + videoId + ".m3u8"
let configuration: JWConfig = JWConfig(contentURL: playerURL)
configuration.controls = true
configuration.autostart = true
configuration.image = imageURL
jwPlayer = JWPlayerController(config: configuration)
jwPlayer?.forceFullScreenOnLandscape = true
jwPlayer?.forceLandscapeOnFullScreen = true
jwPlayer?.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
jwPlayer?.view?.frame = bounds
jwPlayer?.delegate = self
jwPlayer?.volume = 0.0
if let view = jwPlayer?.view {
addSubview(view)
}
}
2. Solution (keep the player and the view instance and reset the configuration of the player)
private func setupPlayer() {
guard let videoId = self.videoId else { return }
let playerURL = jwPlayerURL + videoId + ".m3u8"
let configuration: JWConfig = JWConfig(contentURL: playerURL)
configuration.controls = true
configuration.autostart = true
configuration.image = imageURL
if jwPlayer == nil {
jwPlayer = JWPlayerController(config: configuration)
jwPlayer?.forceFullScreenOnLandscape = true
jwPlayer?.forceLandscapeOnFullScreen = true
jwPlayer?.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
jwPlayer?.view?.frame = bounds
jwPlayer?.delegate = self
jwPlayer?.volume = 0.0
if let view = jwPlayer?.view {
addSubview(view)
}
}else{
//reset the configuration of the player here. but i dont now how this is possible with jwPlayer
}
}

Working code fails when imported into project

I have a Scroll View tutorial that I customized to download images, rather than using local.
The project works fine. When I import the two files and two corresponding View Controllers into my main project it crashes. I'm getting the error
unexpectedly found nil while unwrapping an Optional value.
I've set break points throughout the project, also set exception breakpoint, but can't locate the issue. The Scroll View presents the first image then bombs.
First VC:
import UIKit
import Alamofire
import AlamofireImage
class TutorialViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var pages = [TutorialStepViewController]()
var stringURLS: [String] = []
var networkStatus = NetworkStatus.searching
var constraintString = ""
enum NetworkStatus {
case searching
case finishedSearching
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
getBrewerHistory(portalID: "7", galleryName: "Brewer History")
activityIndicator.startAnimating()
}
func configureScrollView() {
for i in 0..<stringURLS.count {
let page = createAndAddTutorialStep(stringURLS[i], iconImageName: "", text: "")
pages.append(page)
}
pageControl.numberOfPages = pages.count
var views: [String: UIView] = ["view": view]
for i in 0..<pages.count {
views.updateValue(pages[i].view, forKey: "page" + String(i))
}
let metrics = ["edgeMargin": 10, "betweenMargin": 20]
let verticalConstraints =
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[page0(==view)]|", options: [], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints)
for page in 0..<pages.count {
switch page {
case 0:
constraintString += "H:|-edgeMargin-[page" + String(page) + "(==view)]-betweenMargin-"
case pages.count-1:
constraintString += "[page" + String(page) + "(==view)]-edgeMargin-|"
default:
constraintString += "[page" + String(page) + "(==view)]-betweenMargin-"
}
}
let horizontalConstraints =
NSLayoutConstraint.constraints(
withVisualFormat: constraintString, options: [.alignAllTop, .alignAllBottom], metrics: metrics, views: views)
NSLayoutConstraint.activate(horizontalConstraints)
}
fileprivate func createAndAddTutorialStep(_ stringURL: String, iconImageName: String, text: String) -> TutorialStepViewController {
let tutorialStep = storyboard!.instantiateViewController(withIdentifier: "TutorialStepViewController") as! TutorialStepViewController
tutorialStep.view.translatesAutoresizingMaskIntoConstraints = false
let stringURL = stringURL
// tutorialStep.backgroundImage = UIImage(named: backgroundImageName)
let imageURL = URL(string: stringURL)
tutorialStep.backgroundImageURL = imageURL
tutorialStep.iconImage = UIImage(named: iconImageName)
tutorialStep.text = text
scrollView.addSubview(tutorialStep.view)
addChildViewController(tutorialStep)
tutorialStep.didMove(toParentViewController: self)
return tutorialStep
}
}
extension TutorialViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollView.bounds.width
let pageFraction = scrollView.contentOffset.x / pageWidth
pageControl.currentPage = Int(round(pageFraction))
}
}
//Mark: Networking Calls
extension TutorialViewController {
func getBrewerHistory (portalID: String, galleryName: String) {
networkStatus = .searching
activityIndicator.isHidden = false
activityIndicator.startAnimating()
let apiURL = "http://www.smarttapp.com/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=GetBrewerHistory"
let params = ["PortalID" : portalID,
"GalleryName" : galleryName
]
Alamofire.request(
apiURL,
method: .get,
parameters: params
)
.responseJSON { response in
guard response.result.isSuccess else {
print("Error while fetching JSON: \(String(describing: response.result.error))")
return
}
guard let responseArray = response.result.value as? [NSDictionary] else { return }
for dict in responseArray {
if let url = dict["fileName"] as? String {
let fullURL = "http://www.smarttapp.com" + url
self.stringURLS.append(fullURL)
}
}
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.networkStatus = .finishedSearching
self.configureScrollView()
}
}
}
}
Second VC:
import UIKit
class TutorialStepViewController: UIViewController {
#IBOutlet fileprivate weak var backgroundImageView: UIImageView!
#IBOutlet fileprivate weak var iconImageView: UIImageView!
#IBOutlet fileprivate weak var textLabel: UILabel!
var backgroundImage: UIImage?
var backgroundImageURL: URL?
var iconImage: UIImage?
var text: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backgroundImageView.af_setImage(withURL: backgroundImageURL!)
iconImageView.image = iconImage
if let text = text {
let font = UIFont(name: "HelveticaNeue-Light", size:20.0)!
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8
paragraphStyle.alignment = .center
textLabel.attributedText = NSAttributedString(string: text,
attributes: [NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle])
}
}
}

Resources