I really stuck with the problem that my Today Extension widget is not fetching data from Firebase, when it's going to be appeared on screen.
So now I have:
import UIKit
import NotificationCenter
import SwiftUI
import Firebase
class TodayViewController: UIViewController, NCWidgetProviding {
#StateObject var databaseManager = TodayWidgetDatabaseManager()
#StateObject var contacts = FetchContacts() //class for fetching contacts on the phone
override func loadView() {
FirebaseApp.configure()
do {
try Auth.auth().useUserAccessGroup("test.app.id")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
if #available(iOS 10.0, *) {
self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded
} else {
self.preferredContentSize = CGSize(width: 0, height: 400.0)
}
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
databaseManager.fetchIncomingPolls(contacts: contacts.contacts, completionHandler: {_ in})
}
override func viewWillAppear(_ animated: Bool) {
databaseManager.fetchIncomingPolls(contacts: contacts.contacts, completionHandler: {_ in})
}
#IBSegueAction func addSwiftUIView(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(
coder: coder,
rootView: TodayWidgetView(databaseManager: self.databaseManager, contacts: self.contacts)
)
}
override func viewWillDisappear(_ animated: Bool) {
databaseManager.fetchIncomingPolls(contacts: contacts.contacts, completionHandler: {_ in})
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void) = {result in return}) {
databaseManager.fetchIncomingPolls(contacts: contacts.contacts) { _ in
completionHandler(NCUpdateResult.newData)
}
}
#available(iOSApplicationExtension 10.0, *)
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
if activeDisplayMode == .expanded {
preferredContentSize = CGSize(width: maxSize.width, height: 275.0)
} else if activeDisplayMode == .compact {
preferredContentSize = maxSize
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I tried to implement fetching data in onAppear() method of SwiftUI view of the widget, also I tried to use my method for fetching data in viewDidLoad() and viewWillAppear() methods in controller. But none of this helped, so widget is only shows "Unable to load" label.
Related
I am working on a video editor app where I need a trimmer for the video. So I found this package. Now When I'm trying to show the TrimView,but it's not showing up on my SwiftUI view.
This is my View Controller Representable
final class TrimView: UIViewControllerRepresentable{
let player: AVPlayer
init(player: AVPlayer){
self.player = player
}
func makeUIViewController(context: Context) -> some UIViewController {
return TrimVC(player: player)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Here's my View Controller
class TrimVC: UIViewController, TrimmerViewDelegate{
func didChangePositionBar(_ playerTime: CMTime) {
//
}
func positionBarStoppedMoving(_ playerTime: CMTime) {
//
}
let player: AVPlayer
init(player: AVPlayer){
self.player = player
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var trimmerView: TrimmerView = TrimmerView()
override func viewDidLoad() {
trimmerView.delegate = self
view.addSubview(trimmerView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadTrimmer()
}
func loadTrimmer() {
let frame = view.frame.inset(by: view.safeAreaInsets)
let viewWidth = frame.size.width
trimmerView.asset = player.currentItem?.asset
// THIS IS PRINTING MY CURRENT ASSET DIRECTORY.
print("Asset: \(player.currentItem?.asset)")
}
}
And This is How I'm calling it to my SwiftUI View
TrimView(player: player)
.background(Color.red)
.frame( height: UIScreen.screenHeight * 0.05)
But the trimmerView is not showing on my view but only the red square which I put to see if the view is available.
I have created this simple example, it is a UITextField with an autocompletion ability, displaying a table view showing asynchronous data that evolves as the user types in the text field.
TextField
import UIKit
protocol TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField,
_ components: #escaping ([String]) -> Void
)
}
class TextField: UITextField {
var components: [String] = []
var tableView = UITableView(frame: .zero)
var autocompletionDelegate: TextFieldDelegate? { didSet { setupUI() } }
// Actions
#objc private func didUpdateText() {
autocompletionDelegate?.autocompletedComponents(self) { [weak self] components in
guard let weakSelf = self else {
return
}
weakSelf.components = components
weakSelf.tableView.reloadData()
weakSelf.updateUI()
}
}
// Event
override func becomeFirstResponder() -> Bool {
tableView.isHidden = false
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
tableView.isHidden = true
return super.resignFirstResponder()
}
// Init
override init(frame: CGRect) {
super.init(frame: frame)
internalInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
internalInit()
}
private func internalInit() {
addTarget(
self,
action: #selector(didUpdateText),
for: .editingChanged
)
}
// UI
private func setupUI() {
tableView.dataSource = self
tableView.delegate = self
tableView.showsVerticalScrollIndicator = false
tableView.removeFromSuperview()
superview?.addSubview(tableView)
}
private func updateUI() {
let tableViewHeight = Double(min(5, max(0, components.count))) * 44.0
tableView.frame = CGRect(
origin: CGPoint(
x: frame.origin.x,
y: frame.origin.y + frame.size.height
),
size: CGSize(
width: frame.size.width,
height: tableViewHeight
)
)
}
}
extension TextField: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = components[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
44.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
components.count
}
}
ViewController
import MapKit
import UIKit
class ViewController: UIViewController {
private var completion: (([MKLocalSearchCompletion]) -> Void)?
private var searchCompleter = MKLocalSearchCompleter()
#IBOutlet weak var textField: TextField!
override func viewDidLoad() {
super.viewDidLoad()
searchCompleter.delegate = self
textField.autocompletionDelegate = self
}
}
extension ViewController: TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField,
_ components: #escaping ([String]) -> Void
) {
if completion == nil {
completion = { results in
components(results.map { $0.title })
}
}
searchCompleter.queryFragment = textField.text ?? ""
}
}
extension ViewController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results)
}
}
In this example the view controller uses data from MapKit. Now I would like to get rid of the #escaping blocks and replace them with the new async/await syntax.
I have started rewriting the TextField code:
protocol TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField
) async -> [String]
}
#objc private func didUpdateText() {
Task {
let autocompletedComponents = await autocompletionDelegate?.autocompletedComponents(self) ?? []
components = autocompletedComponents
tableView.reloadData()
updateUI()
}
}
However I am stuck in the ViewController, because I don't know what to do with the completion block I was using until now.
Thank you for your help
Here's an implementation using Combine's PassThroughSubject to send the array from MKLocalSearchCompleterDelegate to your autocompletedComponents function which then returns that array to be used in TextField
In ViewController:
class ViewController: UIViewController {
private var searchCompleter = MKLocalSearchCompleter()
var cancellables = Set<AnyCancellable>()
var publisher = PassthroughSubject<[String], Never>()
#IBOutlet weak var textField: TextField!
override func viewDidLoad() {
super.viewDidLoad()
searchCompleter.delegate = self
textField.autocompletionDelegate = self
}
}
extension ViewController: TextFieldDelegate {
func autocompletedComponents(
_ textField: TextField
) async -> [String] {
searchCompleter.queryFragment = textField.text ?? ""
return await withCheckedContinuation { continuation in
publisher
.sink { array in
continuation.resume(returning: array)
}.store(in: &cancellables)
}
}
}
extension ViewController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
publisher.send(completer.results.map({$0.title}))
}
}
Or you could use your completion closure it would look like this:
func autocompletedComponents(
_ textField: TextField,
_ components: #escaping ([String]) -> Void
) {
return await withCheckedContinuation { continuation in
if completion == nil {
completion = { results in
continuation.resume(returning: results.map { $0.title })
}
}
searchCompleter.queryFragment = textField.text ?? ""
}
}
I have problem with PageViewController dots indicators. Actually with background. I tried UIColor.clear but this didn't help. Background still white. I can change the color but I don't know how to remove it or make it transparent.
Can't find a way how to make it transparent.
Screenshot attached.
How to make background color transparent or remove it?
ViewController.swift
import Foundation
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
// Hide status bar.
override var prefersStatusBarHidden : Bool {
return true
}
var pageViewController: UIPageViewController!
let pages = ["DomesticPageViewController", "SafariPageViewController", "ForestPageViewController"]
// Page View Controller Datasource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = pages.index(of: viewController.restorationIdentifier!) {
if index > 0 {
return viewControllerAtIndex(index - 1)
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = pages.index(of: viewController.restorationIdentifier!) {
if index < pages.count - 1 {
return viewControllerAtIndex(index + 1)
}
}
return nil
}
func viewControllerAtIndex(_ index: Int) -> UIViewController? {
let vc = storyboard?.instantiateViewController(withIdentifier: pages[index])
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let vc = storyboard?.instantiateViewController(withIdentifier: "MyPageViewController") {
self.addChildViewController(vc)
self.view.addSubview(vc.view)
pageViewController = vc as! UIPageViewController
pageViewController.dataSource = self
pageViewController.delegate = self
pageViewController.setViewControllers([viewControllerAtIndex(0)!], direction: .forward, animated: true, completion: nil)
pageViewController.didMove(toParentViewController: self)
}
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.lightGray
appearance.currentPageIndicatorTintColor = UIColor.black
appearance.backgroundColor = UIColor.clear
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
setupPageControl()
return self.pages.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
DomesticViewController.swift
import Foundation
import UIKit
class DomesticPageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let backgroundImage = UIImageView(frame: UIScreen.main.bounds)
backgroundImage.image = UIImage(named: "domestic_bg1")
backgroundImage.contentMode = UIViewContentMode.scaleAspectFill
self.view.insertSubview(backgroundImage, at: 0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I have 2 UIViewControllers as items of UIPageViewController. It works well for slide, but i want to move from pageOne to pageTwo with button's action. How i can do that?
Here is my code and storyboard preview:
Contents of Pager.swift :
import UIKit
class Pager: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, UIScrollViewDelegate
{
var viewsArray: [UIViewController] = []
var pageOne: UIViewController = UIViewController()
var pageTwo: UIViewController = UIViewController()
var currentIndex = 0
var previousIndex = 0
var nextIndex = 0
override func viewDidLoad()
{
super.viewDidLoad()
self.delegate = self
self.dataSource = self
pageOne = (storyboard?.instantiateViewController(withIdentifier: "pageOne"))!
pageTwo = (storyboard?.instantiateViewController(withIdentifier: "pageTwo"))!
viewsArray.append(pageOne)
viewsArray.append(pageTwo)
setViewControllers([pageOne], direction: .forward, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
currentIndex = viewsArray.index(of: viewController)!
if currentIndex == 1
{
return nil
}
else
{
nextIndex = abs((currentIndex + 1) % viewsArray.count)
return viewsArray[nextIndex]
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
currentIndex = viewsArray.index(of: viewController)!
if currentIndex == 0
{
return nil
}
else
{
previousIndex = abs((currentIndex - 1) % viewsArray.count)
return viewsArray[previousIndex]
}
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return viewsArray.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
}
Contents of PageOne.swift :
import UIKit
class PageOne: UIViewController
{
var pager = Pager()
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func forwardButton(_ sender: Any)
{
pager.setViewControllers([pager.pageOne], direction: .forward, animated: true, completion: nil)
}
}
You need to write below line
pageController?.setViewControllers([YOUR_VIEW_CONTROLLER], direction: direction, animated: true, completion: nil)
give proper direction either forward or reverse based on which controller you are at
hope this will help you
You need to pass pager to your both viewcontrollers
pageOne.pager = self
pageTwo.pager = self
I want to retrieve the value from the scrollViewDidScroll function at my view controller. It gives the right value back in the console so that's nice.
My view controller:
import UIKit
extension UIPageViewController: UIScrollViewDelegate {
public override func viewDidLoad() {
super.viewDidLoad()
for subView in view.subviews {
if subView is UIScrollView {
(subView as! UIScrollView).delegate = self
}
}
}
public func scrollViewDidScroll(scrollView: UIScrollView) {
let point = scrollView.contentOffset
var percentComplete: CGFloat
percentComplete = fabs(point.x - view.frame.size.width)/view.frame.size.width
print("percentComplete: ", percentComplete)
}
}
class StepsDetailViewController: UIViewController {
var pageIndex: Int!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Where the view controller came from:
import UIKit
class PageControl: UIPageViewController, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController!
var pageTitles: [[String]]!
override func viewDidLoad(){
super.viewDidLoad()
testC().getRecent() { result, error in
self.pageTitles = result;
self.pageViewController = self
self.pageViewController.dataSource = self
let startVC = self.viewControllerAtIndex(0) as StepsDetailViewController
dispatch_async(dispatch_get_main_queue(), {
let viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewControllerAtIndex(index: Int) -> StepsDetailViewController
{
if ((self.pageTitles.count == 0) || (index >= self.pageTitles.count)) {
return StepsDetailViewController()
}
let vc: StepsDetailViewController = self.storyboard?.instantiateViewControllerWithIdentifier("StepsDetailViewController") as! StepsDetailViewController
vc.pageIndex = index
return vc
}
// MARK: - Page View Controller Data Source
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let vc = viewController as! StepsDetailViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound)
{
return nil
}
index -= 1
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! StepsDetailViewController
var index = vc.pageIndex as Int
if (index == NSNotFound)
{
return nil
}
index += 1
if (index == self.pageTitles.count)
{
return nil
}
return self.viewControllerAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
}
Is this possible?
You could do like this:
1. Replace your extension with a custom UIPageViewController
class BasePageViewController: UIPageViewController, UIScrollViewDelegate {
var percentComplete: CGFloat = 0.0 {
didSet { self.percentCompleteDidChange() }
}
func percentCompleteDidChange() {
print("percentCompleteDidChange")
}
public override func viewDidLoad() {
super.viewDidLoad()
for subView in view.subviews {
if subView is UIScrollView {
(subView as! UIScrollView).delegate = self
}
}
}
public func scrollViewDidScroll(scrollView: UIScrollView) {
let point = scrollView.contentOffset
self.percentComplete = fabs(point.x - view.frame.size.width)/view.frame.size.width
print("percentComplete: ", percentComplete)
}
}
2. PageControl is a BasePageViewController subclass
class PageControl: BasePageViewController, UIPageViewControllerDataSource {
// here you have the access to percentComplete of superclass
func printPercentComplete() {
if let percentComplete = self.percentComplete {
print(percentComplete)
}
}
override func percentCompleteDidChange() {
print("percentCompleteDidChange from child")
}
......
}
Define the percentComplete as a property of your custom class that inherits from UIPageViewController.
Then you can access it everywhere.