I'm trying to implement 3D Touch Peek and Pop in my swift code. When the user presses deeper on the peek view, array of preview actions will appear (Share, Update, Delete).
What I need is when user select Update action will move to UpdateView controller, but it keeps on crashing.
Here is my code:
HomePeakViewController.swift
let item3 = UIPreviewAction(title: "Update", style: .Default) { (action:UIPreviewAction, vc:UIViewController) -> Void in
print("Update")
let nb:BookAppointmentViewController = BookAppointmentViewController(nibName: "BookAppointmentViewController", bundle: nil)
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
root?.presentViewController(nb, animated: true, completion: nil)
POP method in HomeViewController.swift
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
let Homepeak = HomePeakViewController()
showViewController(Homepeak, sender: self)
}
I tried this code as well to move to Update screen, but it gives me (fatal error: unexpectedly found nil while unwrapping an Optional value).
var top = UIApplication.sharedApplication().keyWindow?.rootViewController
let test = AppointmentDetailsViewController()
top!.presentViewController(test, animated: true, completion: {})
Maybe you need to deal with delegations:
For example:
extension MainViewController: UIViewControllerPreviewingDelegate {
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if #available(iOS 9.0, *) {
previewingContext.sourceRect = myButton!.bounds //optional
}
let homePeakViewController = UIStoryboard.homePeakViewController()
homePeakViewController.delegate = self
return homePeakViewController
}
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
let balanceViewController = viewControllerToCommit as! HomePeakViewController
navigationController?.pushViewController(balanceViewController, animated: true)
}
}
extension MainViewController: HomePeakViewControllerDelegate {
func homePeakViewControllerUpadateActionTapped() {
let bookAppointmentViewController = let nb:BookAppointmentViewController = BookAppointmentViewController(nibName: "BookAppointmentViewController", bundle: nil)
navigationController?.pushViewController(bookAppointmentViewController, animated: true) //present as you want
}
}
protocol HomePeakViewControllerDelegate {
func homePeakViewControllerUpadateActionTapped()
}
class HomePeakViewController {
var delegate: HomePeakViewControllerDelegate?
#available(iOS 9.0, *)
override func previewActionItems() -> [UIPreviewActionItem] {
let item3 = UIPreviewAction(title: "Update", style: .Default) { (action:UIPreviewAction, vc:UIViewController) -> Void in
delegate?.homePeakViewControllerUpadateActionTapped()
}
return [item3]
}
}
Related
Is that possible to push vc over the PHPickerViewController?
I'm trying to do that like this with no luck:
var configuration = PHPickerConfiguration()
configuration.filter = .any(of: [.images, .livePhotos])
photoPickerController = PHPickerViewController(configuration: configuration)
photoPickerController.delegate = self
present(self.photoPickerController, animated: true)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
//Push segue
performSegue(withIdentifier: "showAddPost", sender: self)
}
Update - Using CocoaPods
I created a simple pod PhotoCropController that includes a basic photo crop controller to be presented from the PHPickerViewControllerDelegate. It provides transitions to push to a modally presented controller and to pop or dismiss. The aspect ratio of the crop view can be edited as well. To use conform your view controller to the PhotoCropDelegate protocol and present the PhotoCropController from your PHPickerViewControllerDelegate. Implementation would look something like the following:
extension ViewController: PHPickerViewControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, PhotoCropDelegate {
func browsePhotoLibrary() {
if #available(iOS 14, *) {
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
config.selectionLimit = 1
config.preferredAssetRepresentationMode = .compatible
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
let nav = UINavigationController(rootViewController: picker)
nav.setNavigationBarHidden(true, animated: false)
nav.setToolbarHidden(true, animated: true)
present(nav, animated: true) } else {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let edited = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
image = edited
} else if let selected = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image = selected
}
presentedViewController?.dismiss(animated: true)
}
#available(iOS 14, *)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if let provider = results.last?.itemProvider,
provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { [weak self] result, error in
if let image = result as? UIImage {
DispatchQueue.main.async { self?.select(image: image) }
} else if let error = error {
NSLog("Error picking image: %#", error.localizedDescription)
DispatchQueue.main.async { picker.dismiss(animated: true) }
}
}
} else { DispatchQueue.main.async { picker.dismiss(animated: true) } }
}
func select(image: UIImage) {
let destinationSize = AVMakeRect(aspectRatio: image.size, insideRect: view.frame).integral.size
//Best Performance
let resizedImage = UIGraphicsImageRenderer(size: destinationSize).image { (context) in
image.draw(in: CGRect(origin: .zero, size: destinationSize))
}
let cropController = PhotoCropController()
cropController.delegate = self
cropController.image = resizedImage
presentedViewController?.present(cropController, animated: true)
}
#objc func cropViewDidCrop(image: UIImage?) {
self.image = image
presentedViewController?.dismiss(animated: true) { [weak self] in
self?.presentedViewController?.dismiss(animated: true)
}
}
}
To present another controller modally in front of the PHPickerViewController:
The presentedViewController property of your view controller will be your photoPickerController so you can present another controller in front of it as follows:
present(photoPickerController, animated: true)
presentedViewController?.present(yourViewController, animated: true)
If dismissing from the presentedViewController, you will need to call twice, once to dismiss yourViewController and then again to dismiss the photoPickerController, if they are both presented:
presentedViewController?.dismiss(animated: true)
presentedViewController?.dismiss(animated: true)
Pushing a custom controller on top of the navigation stack
To create the appearance of pushing to the PHPickerViewController stack you can use custom UIPresentationController and UIViewControllerAnimatedTransitioning classes to present your view. The following classes will mimic a push to a modally presented navigation controller:
import UIKit
import PhotosUI
class SlideInModalPresentationController: UIPresentationController {
var offset: CGFloat = 0.0
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame.origin.y = offset
presentedView?.frame.size.height -= offset
presentedView?.layer.cornerRadius = 10
presentedView?.clipsToBounds = true
}
}
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
private let duration = 0.3
var isPresenting: Bool = true
var dismissModally: Bool = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toController = transitionContext.viewController(forKey: .to),
let fromController = transitionContext.viewController(forKey: .from)
else { return}
if isPresenting {
toController.view.frame.origin.x = fromController.view.frame.width
transitionContext.containerView.addSubview(toController.view)
UIView.animate(withDuration: duration, animations: {
toController.view.frame.origin.x = 0
}, completion: { _ in
transitionContext.completeTransition(true)
})
} else if dismissModally {
var stack: UIView? = nil
if #available(iOS 14, *), toController is PHPickerViewController {
stack = toController.view.superview
toController.dismiss(animated: false)
} else if toController is UIImagePickerController {
stack = toController.view.superview
toController.dismiss(animated: false)
}
UIView.animate(withDuration: duration, animations: {
stack?.frame.origin.y = fromController.view.frame.height
fromController.view.frame.origin.y = fromController.view.frame.height
}, completion: { _ in
transitionContext.completeTransition(true)
fromController.view.removeFromSuperview()
})
} else {
UIView.animate(withDuration: duration, animations: {
fromController.view.frame.origin.x = fromController.view.frame.width
}, completion: { _ in
transitionContext.completeTransition(true)
fromController.view.removeFromSuperview()
})
}
}
}
To implement in your view controller:
class ViewController: UIViewController {
let slidInTransition = SlideInTransition()
}
extension ViewController: UIViewControllerTransitioningDelegate {
private func presentYourController(_ image: UIImage) {
let yourController = YourController()
yourController.image = image
yourController.modalPresentationStyle = .custom
yourController.transitioningDelegate = self
slidInTransition.dismissModally = false
presentedViewController?.present(yourController, animated: true)
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = SlideInModalPresentationController(presentedViewController: presented, presenting: presenting)
presentationController.offset = view.convert(source.view.frame, to: nil).origin.y + 10
return presentationController
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
slidInTransition.isPresenting = true
return slidInTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
slidInTransition.isPresenting = false
return slidInTransition
}
private func dismissPhotoStack() {
slidInTransition.dismissModally = true
presentedViewController?.dismiss(animated: true)
}
}
When you are ready to dismiss the whole stack you can call dismissPhotoStack.
I would like to implement routing in Swift like ReactJS, I have implemented protocols to serve for Routing.
But it's getting crashed in UIViewController extention. Can anyone help me with the solution.
Here is my code.
import Foundation
import UIKit
extension UIViewController {
func presented(_ animated: Bool) {
print("\(#function)")
present(Route.destination, animated: animated,
completion: nil)
}
func pushed(_ animated: Bool) {
print("\(#function)")
_ = navigationController?.pushViewController(Route.destination,
animated: true)
}
}
protocol Router {
static func toController <T: UIViewController>(_ controller:T,
params: Any) -> T
}
class Route : Router {
static var destination: UIViewController!
static func toController<T:UIViewController>(_ controller: T,
params: Any) -> T {
let viewController : T = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: String(describing: T.self)) as! T
destination = viewController
return viewController
}
}
class ViewController: UIViewController {
#IBAction func navigate() {
Route.toController(SecondViewControlller(), params: [])
.presented(true)
}
}
The app is crashing because you are going to present the same
viewController on itself.
The reason is below method takes destination viewController as an argument and return itself as destination.
static func toController <T: UIViewController>(_ controller:T, params: Any) -> T
In addition to that, whenever presented(_ animated: Bool) gets called from Route.toController(SecondViewControlller(), params: []).presented(true), self and Route.destination are same. So, it leads to presenting the same viewController on itself and causing sort of below error or crashing the application.
Attempt to present on
whose view is not in the
window hierarchy!
Try this:
extension UIViewController {
func presented(_ animated: Bool) {
print("\(#function)")
self.present(Route.destination, animated: animated, completion: nil)
}
func pushed(_ animated: Bool) {
print("\(#function)")
_ = self.navigationController?.pushViewController(Route.destination, animated: true)
}
}
protocol Router {
static func toController <T: UIViewController, T2: UIViewController>(_ controller: T2, from source: T, params: Any) -> T
}
class Route : Router {
static var destination: UIViewController!
static func toController <T: UIViewController, T2: UIViewController>(_ controller: T2, from source: T, params: Any) -> T {
let viewController : T2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: String(describing: T2.self)) as! T2
destination = viewController
return source
}
}
//Your ViewController.swift
#IBAction func onButtonTap(_ sender: Any) {
Route.toController(SecondViewControlller(), from: self, params: []).presented(true)
}
I am having issues trying to pass the data back to the ViewController (from BarCodeScannerViewController to TableViewController)
SecondVC (BarCodeScannerViewController.swift):
#objc func SendDataBack(_ button:UIBarButtonItem!) {
if let presenter = self.presentingViewController as? TableViewController {
presenter.BarCode = "Test"
}
self.dismiss(animated: true, completion: nil)
}
FirstVC (TableViewController.swift):
// The result is (BarCode - )
var BarCode: String = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("BarCode - \(BarCode)")
}
Each time ViewWillAppear is running the value is not set, what could be causing this issue?
You should use the delegate pattern. I doubt in your code above that self.presentingViewController is actually set.
An example of using the delegate pattern for this:
// BarCodeScannerViewController.swift
protocol BarcodeScanningDelegate {
func didScan(barcode: String)
}
class BarCodeScannerViewController: UIViewController {
delegate: BarcodeScanningDelegate?
#objc func SendDataBack(_ button:UIBarButtonItem!) {
delegate?.didScan(barcode: "Test")
}
}
// TableViewController
#IBAction func scanBarcode() {
let vc = BarCodeScannerViewController()
vc.delegate = self
self.present(vc, animated: true)
}
extension TableViewController: BarcodeScanningDelegate {
func didScan(barcode: String) {
print("[DEBUG] - Barcode scanned: \(barcode)")
}
}
I've got 3 methods that are very similar in structure, I would like to extract a common method, looking like this:
private func navigateToViewController(animated: Bool, viewControllerType: T, viewControllerNibName: String, mode: MenuMode) { ... }
however, I don't know how to handle the type parameter, any good suggestions? Thanks!
private func navigateToEditorView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.editorViewController == nil {
let editor = EditorViewController(nibName:"EditorViewController", bundle: nil)
editor.exitCallBack = self.setBackgroundImage
self.editorViewController = editor
}
if let editor = self.editorViewController {
self.navigationController?.pushViewController(editor, animated: animated)
}
self.currentMenuMode = .editor
}
private func navigateToStorageView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.storageViewController == nil {
let storage = StorageViewController(nibName:"StorageViewController", bundle: nil)
storage.exitCallBack = self.setBackgroundImage
self.storageViewController = storage
}
if let storage = self.storageViewController {
self.navigationController?.pushViewController(storage, animated: animated)
}
self.currentMenuMode = .storage
}
private func navigateToGalleryView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.galleryViewController == nil {
let gallery = GalleryViewController(nibName:"GalleryViewController", bundle: nil)
gallery.exitCallBack = self.setBackgroundImage
self.galleryViewController = gallery
}
if let gallery = self.galleryViewController {
self.navigationController?.pushViewController(gallery, animated: animated)
}
self.currentMenuMode = .gallery
}
I think using protocol is the best way to handle all of your questions
enum MenuMode {
case editor
case storage
case gallery
}
protocol ExitCallBackHandler where Self: UIViewController {
var exitCallBack: (() -> Void)? { get set }; // I don't know what it is
var currentMenuMode: MenuMode { get }
}
Interface of each ViewController
class EditorViewController: UIViewController, ExitCallBackHandler {
var exitCallBack: (() -> Void)?
var currentMenuMode: MenuMode {
return .editor
}
// ...
}
class StorageViewController: UIViewController, ExitCallBackHandler {
var exitCallBack: (() -> Void)?
var currentMenuMode: MenuMode {
return .storage
}
// ...
}
class GalleryViewController: UIViewController, ExitCallBackHandler {
var currentMenuMode: MenuMode {
return .gallery
}
var exitCallBack: (() -> Void)?
// ...
}
Finally
private func navigateToViewController<T: ExitCallBackHandler>(animated: Bool, viewControllerType: T.Type) {
self.dismiss(animated: false, completion: nil)
var vc = T(nibName: String(describing: viewControllerType), bundle: nil)
vc.exitCallBack = ...
self.currentMenuMode = vc.currentMenuMode
self.navigationController?.pushViewController(vc, animated: animated)
}
Use it like this way:
navigateToViewController(animated: true, viewControllerType: StorageViewController.self)
I think your function just needs to be generic over T:
func navigateToViewController<T: UIViewController>(viewControllerType: T, nibName: String) {
let vc = T(nibName: nibName, bundle: nil)
print(vc)
}
Make your controllers conform to protocol Exitable
protocol Exitable{
var exitCallBack: (()->Void)? {get set}
}
Make your controllers conform to that:
class EditorViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
class StorageViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
class GalleryViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
Now make func:
func navigateToViewController<T: UIViewController & Exitable>(viewControllerType: T?, nibName: String, withExitBlock exitBlock: #escaping ()->Void, animated: Bool) {
var vc = T(nibName: nibName, bundle: nil)
vc.exitCallBack = exitBlock
self.navigationController?.pushViewController(vc, animated: animated)
}
This is my first attempt at an IOS app, and I have no experience with swift, and a lot of the code is borrowed from the web and edited.
I am trying to create a set of slides. I go from the main Landing page to another View Controller, TestVC, that runs the slides. The landing page and the slides work. I can swipe back and forth. I am now trying to add a timer so that the slides auto advance every 5 or so seconds.
I believe that the code that needs to be run is:
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
I get an error :
test.swift:31:9: Ambiguous reference to member 'pageViewController(_:viewControllerBefore:)'.
I do not know how to interpret this error and move forward. The error is triggered in the test.swift, where a timer calls a function that tries to advance the slide. Advise is appreciated. If I am doing it wrong, please point me in the appropriate direction.
The landing page has a button, that opens a ViewController testVC. I have 2 files, test.swift and alphabetItemController.swift. The storyboard has, in addition to the landing page ViewController, a PageViewController called alphabetPVC, a ViewController called alphabetVC and a ViewController called TestVC.
Here is the code for alphabetItemController.swift ...
import UIKit
class alphabetItemController: UIViewController {
#IBOutlet weak var contentImageView2: UIImageView!
#IBOutlet weak var contentWordPn: UILabel!
var itemIndex: Int = 0
var imageName: String = ""
var wordPN: String = ""
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
contentImageView2!.image = UIImage(named: imageName)
contentWordPn!.text = wordPN
}
}
Here is the code for test.swift ...
import Foundation
import UIKit
class testItemController: UIViewController, UIPageViewControllerDataSource {
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(changeSlide), userInfo: nil, repeats: true)
//tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(goToNextPage), userInfo: nil, repeats: true)
}
func changeSlide() {
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
}
// MARK: - Variables
private var varPageVC: UIPageViewController?
private let contentTextWordPN = ["A", "B", "C", "D", "E"]
private let contentCount = 5 //TODO ADJUST THIS FOR EACH COLLECTION
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetPVC") as! UIPageViewController
pageController.dataSource = self
if contentCount > 0 {
let firstController = getItemController(itemIndex: 0)!
let startingViewControllers = [firstController]
pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
varPageVC = pageController
addChildViewController(varPageVC!)
self.view.addSubview(varPageVC!.view)
varPageVC!.didMove(toParentViewController: self)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.gray
appearance.currentPageIndicatorTintColor = UIColor.white
appearance.backgroundColor = UIColor.darkGray
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex > 0 {
return getItemController(itemIndex: itemController.itemIndex-1)
}
return nil
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex+1 < contentCount {
return getItemController(itemIndex: itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> alphabetItemController? {
if itemIndex < contentCount {
let pageItemController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetVC") as! alphabetItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = "alphabet_" + String(format: "%02d", (itemIndex + 1)) //alphabet_01
pageItemController.wordPN = contentTextWordPN[itemIndex]
return pageItemController
}
return nil
}
func presentationCountForPageViewController(varPageVC: UIPageViewController) -> Int {
return contentCount
}
func presentationIndexForPageViewController(varPageVC: UIPageViewController) -> Int {
return 0
}
func currentControllerIndex() -> Int {
let pageItemController = self.currentController()
if let controller = pageItemController as? alphabetItemController {
return controller.itemIndex
}
return -1
}
func currentController() -> UIViewController? {
if (self.varPageVC?.viewControllers?.count)! > 0 {
return self.varPageVC?.viewControllers![0]
}
return nil
}
}
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}
There is even an extension UIPageViewController, but I do not know how to call the goToNextPage function.
I ended up changing the changeSlide() function... I had the next slide function in the extension already, and only the syntax to call it was eluding me... I found some examples on SO and used them as references:
func changeSlide() {
varPageVC?.goToNextPage()
}