SwiftUI - STPAddCardViewController is not showing billing address - swift

I am trying to implement Add Payment Method with Stripe in SwiftUI. The code works fine but it does not display the field for user Billing address. This is my code
struct AddCardView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentation
// #ObservedObject var model: PaymentMethodViewModel
func makeUIViewController(context: Context) -> UINavigationController {
let controller = STPAddCardViewController()
controller.delegate = context.coordinator
controller.edgesForExtendedLayout = .all
let navigationController = UINavigationController(rootViewController: controller)
navigationController.navigationBar.prefersLargeTitles = true
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, STPAddCardViewControllerDelegate, UINavigationControllerDelegate {
var parent: AddCardView
func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
parent.presentation.wrappedValue.dismiss()
}
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreatePaymentMethod paymentMethod: STPPaymentMethod, completion: #escaping STPErrorBlock) {
print(paymentMethod)
// parent.model.isCards = true
parent.presentation.wrappedValue.dismiss()
}
init(_ parent: AddCardView) {
self.parent = parent
}
}
}
the code above produces this
I thought the Billing information text field will be included in the STPAddCardViewController method like this image
How can fix the code above to include text field for users to enter the card billing address?

you could try using "STPPaymentConfiguration" parameters, and call
let controller = STPAddCardViewController(configuration: ... , theme: ...)

Provide a configuration and a theme in your initialize method.
Here is sample in Objective-C.
STPPaymentConfiguration *configuration = [[STPPaymentConfiguration alloc] init];
configuration.requiredBillingAddressFields = STPBillingAddressFieldsFull;
STPAddCardViewController *addCardVC = [[STPAddCardViewController alloc] initWithConfiguration:configuration theme:[STPTheme defaultTheme]];
addCardVC.alwaysEnableDoneButton = YES;
addCardVC.delegate = self;
vc = addCardVC;
https://github.com/stripe/stripe-ios/blob/591f54d11f6f60be609e294f7dd7570c0ad32b49/LocalizationTester/ViewController.m

Related

how to use UIViewRepresentable Coordinator delegate

I'm using Pulley a maps drawer library which is written in UIKit in a SwiftUI project. I have a SwiftUI ListView that I'm using in the project via a UIHostingController but I want to disable scrolling when the drawers position is not open and to do that I'm pretty sure I need to use one of the delegate functions Pulley provides (drawerPositionDidChange) but I'm not sure how to use the delegate in the Coordinator or if I should even try to use the delegate, maybe I just need to use some type of state variable?
Delegate in the view controller
#objc public protocol PulleyDelegate: AnyObject {
/** This is called after size changes, so if you care about the bottomSafeArea property for custom UI layout, you can use this value.
* NOTE: It's not called *during* the transition between sizes (such as in an animation coordinator), but rather after the resize is complete.
*/
#objc optional func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat)
}
This is the UIViewRepresentable where I'm trying to use the delegate.
import SwiftUI
struct DrawerPosition: UIViewControllerRepresentable {
#Binding var bottomSafeArea: CGFloat?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> some UIViewController {
let vc = PulleyViewController()
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Updates the state of the specified view controller with new information from SwiftUI.
}
class Coordinator: NSObject, PulleyDrawerViewControllerDelegate {
var parent: DrawerPosition
init (_ parent: DrawerPosition) {
self.parent = parent
}
func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat){
self.parent.bottomSafeArea = bottomSafeArea
}
}
}
the ListView where I want to disable the scroll.
import SwiftUI
struct ListView: View {
#State private var bottomSafeArea: CGFloat?
var body: some View {
ScrollViewReader { proxy in
VStack {
Button("Jump to #50") {
proxy.scrollTo(50)
}
List(0..<100, id: \.self) { i in
Text("Example")
.id(i)
}.scrollDisabled(bottomSafeArea == 0 ? true : false)
}
}
}
}
class ListViewVHC: UIHostingController<ListView> {
required init?(coder: NSCoder) {
super.init (coder: coder, rootView: ListView())
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Here is the correct way to set up a Coordinator:
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIViewController(context: Context) -> PullyViewController {
context.coordinator.pullyViewController
}
func updateUIViewController(_ uiViewController: PullyViewController, context: Context) {
// Updates the state of the specified view controller with new information from SwiftUI.
context.coordinator.bottomSafeAreaChanged = { bottomSafeArea in
self.bottomSafeArea = bottomSafeArea
}
}
class Coordinator: NSObject, PulleyDrawerViewControllerDelegate {
lazy var pullyViewController: PulleyViewController = {
let vc = PulleyViewController()
vc.delegate = self
return vc
}()
var bottomSafeAreaChanged: ((CGFloat) -> Void)?
func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat){
bottomSafeAreaChanged?(bottomSafeArea)
}

Directly open new contact screen from app in SwiftUI

Im trying to open New Contact screen in my SwiftUI app but Im not getting Save\Done button.I haven't seen anybody doing this I really don't know answer why as it seems easy at first. This is my current code.
struct ContactsVC: UIViewControllerRepresentable{
typealias UIViewControllerType = CNContactViewController
func makeUIViewController(context: Context) -> CNContactViewController {
let store = CNContactStore()
let con = CNContact()
let vc = CNContactViewController(forNewContact: con)
vc.contactStore = store
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: CNContactViewController, context: Context) {
}
class Coordinator: NSObject,CNContactViewControllerDelegate,UINavigationControllerDelegate{
var parent: ContactsVC
init(_ parent: ContactsVC) {
self.parent = parent
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
I have requested authorisation when sending contact now I want to add it.

FirebaseUI "Sign in with email" does nothing when tapped

I have the following code taken directly from this blog. This code successfully displays the FirebaseUI with Google sign In, Apple sign In, and Email sign in when I place the SignInViewUI into my view.
Google and Apple sign in both function correctly. Email sign in does nothing when tapped.
import Firebase
import FirebaseUI
import SwiftUI
typealias AM = AuthManager
class AuthManager : NSObject{
static let shared = AuthManager()
var authViewController : UIViewController {
return MyAuthViewController(authUI: FUIAuth.defaultAuthUI()!)
}
init(withNavigationBar : Bool = false){
FirebaseApp.configure()
super.init()
self.setupProviders()
}
private func setupProviders(){
let authUI = FUIAuth.defaultAuthUI()!
let providers: [FUIAuthProvider] = [
FUIGoogleAuth.init(authUI: authUI),
FUIOAuth.appleAuthProvider(),
FUIEmailAuth()]
authUI.providers = providers
}
}
extension AuthManager {
// an optional handler closure for error handling
func signOut(onError handler : ((Error?) -> Void)? = nil ){
do {
try FUIAuth.defaultAuthUI()?.signOut()
if let handler = handler {
handler(nil)
}
}
catch (let err){
if let handler = handler {
handler(err)
}
}
}
func isSignedIn() -> Bool {
if let _ = Firebase.Auth.auth().currentUser{
return true
}
return false
}
}
extension AuthManager {
func setAuthDelegate(_ delegate : FUIAuthDelegate){
FUIAuth.defaultAuthUI()?.delegate = delegate
}
}
class MyAuthViewController : FUIAuthPickerViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = view.subviews[0]
scrollView.backgroundColor = .clear
let contentView = scrollView.subviews[0]
contentView.backgroundColor = .clear
view.backgroundColor = .clear
}
}
struct SignInViewUI: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<SignInViewUI>) ->
UIViewController {
return AM.shared.authViewController
}
func updateUIViewController(_ uiViewController: UIViewController,
context: UIViewControllerRepresentableContext<SignInViewUI>) {
// empty
}
}
I believe this is something to do with a navigation controller being required for the Email sign in, based on the final comment on this GitHub issue.
Surrounding the SignInViewUI in a NavigationView allows the button to work.
Secondly, the FirebaseApp.configure() line must be removed from the AuthManager and placed into the AppDelegate application(_ application: UIApplication, didFinishLaunchingWithOptions ... method.

How to present the STPPaymentOptionsViewController in swift ui

.sheet(isPresented: $showSheet) {
STPPaymentOptionsViewController()
}
I run this code hoping to present the Stripe Payment Options View Controller in my content view and I get this error:
Instance method sheet(isPresented:onDismiss:content:) requires that STPAddCardViewController conform to View
I also tried to wrap the view into a UIViewRepresentable like so:
struct PaymentOptionsView: UIViewRepresentable {
func makeUIView(context: Context) -> STPPaymentOptionsViewController {
let config = STPPaymentConfiguration()
config.additionalPaymentOptions = .default
config.requiredBillingAddressFields = .none
config.appleMerchantIdentifier = "dummy-merchant-id"
return STPPaymentOptionsViewController(configuration: config, e: STPTheme(), customerContext: STPCustomerContext(), delegate: self as! STPPaymentOptionsViewControllerDelegate)
}
}
Then I get the error:
Type CheckOut.PaymentOptionsView does not conform to protocol UIViewRepresentable.
Considering that STPPaymentOptionsViewController inherits from ViewController you need to use UIViewControllerRepresentable instead.
You also need to implement the required delegate methods for the STPPaymentOptionsViewControllerDelegate.
struct PaymentOptionsView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, STPPaymentOptionsViewControllerDelegate {
var control: PaymentOptionsView
init(_ control: PaymentOptionsView) {
self.control = control
}
// Implement required delegate methods here:
func paymentOptionsViewControllerDidCancel(_ paymentOptionsViewController: STPPaymentOptionsViewController) {
}
func paymentOptionsViewControllerDidFinish(_ paymentOptionsViewController: STPPaymentOptionsViewController) {
}
func paymentOptionsViewController(_ paymentOptionsViewController: STPPaymentOptionsViewController, didFailToLoadWithError error: Error) {
}
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PaymentOptionsView>) -> STPPaymentOptionsViewController {
let config = STPPaymentConfiguration()
config.additionalPaymentOptions = .default
config.requiredBillingAddressFields = .none
config.appleMerchantIdentifier = "dummy-merchant-id"
return STPPaymentOptionsViewController(configuration: config, theme: STPTheme(), apiAdapter: STPCustomerContext(), delegate: context.coordinator)
}
func updateUIViewController(_ uiViewController: STPPaymentOptionsViewController, context: UIViewControllerRepresentableContext<PaymentOptionsView>) { }
}
Keep in mind you're also setting the delegate in the STPPaymentOptionsViewController incorrectly. You need to use context.coordinator rather than self as! STPPaymentOptionsViewControllerDelegate.

SwiftUI UISearchController searchResultsController navigation stack issue

I have UISearchController that's been made UIViewControllerRepresentable for SwiftUI, as follows:
struct SearchViewController<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
let searchResultsView = SearchResultsView()
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UINavigationController {
let rootViewController = UIHostingController(rootView: content())
let navigationController = UINavigationController(rootViewController: rootViewController)
let searchResultsController = UIHostingController(rootView: searchResultsView)
// Set nav properties
navigationController.navigationBar.prefersLargeTitles = true
navigationController.definesPresentationContext = true
// Create search controller
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchBar.autocapitalizationType = .none
searchController.delegate = context.coordinator
searchController.searchBar.delegate = context.coordinator // Monitor when the search button is tapped.
// Create default view
rootViewController.navigationItem.searchController = searchController
rootViewController.title = "Search"
return navigationController
}
func updateUIViewController(_ navigationController: UINavigationController, context: UIViewControllerRepresentableContext<SearchViewController>) {
//
}
}
This works, and displays the searchResultsController when the user is searching. However, the searchResultsController doesn't seem to know it's navigational context/stack, so I can't navigate from that list view in the searchResultsController.
Can this be structured to allow navigation from searchResultsController, or is this currently a SwiftUI limitation.
Any advice is much appreciated!
I recently also need to implement this feature(search bar like navigation bar) in my app. Looking you snippets really inspired m2! My Thanks First!
After learning from you, ula1990, Lorenzo Boaro and V8tr, I implemented my own.
I set the searchResultsController to nil (due to untappable navigation link in it, so i set it to nil).
Demo
Code:
struct SearchController<Result: View>: UIViewControllerRepresentable {
#Binding var searchText: String
private var content: (_ searchText:String)->Result
private var searchBarPlaceholder: String
init(_ searchBarPlaceholder: String = "", searchedText: Binding<String>,
resultView: #escaping (_ searchText:String) -> Result) {
self.content = resultView
self._searchText = searchedText
self.searchBarPlaceholder = searchBarPlaceholder
}
func makeUIViewController(context: Context) -> UINavigationController {
let contentViewController = UIHostingController(rootView: SearchResultView(result: $searchText, content: content))
let navigationController = UINavigationController(rootViewController: contentViewController)
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = context.coordinator
searchController.obscuresBackgroundDuringPresentation = false // for results
searchController.searchBar.placeholder = searchBarPlaceholder
contentViewController.title = "\\(Title)" // for customization
contentViewController.navigationItem.searchController = searchController
contentViewController.navigationItem.hidesSearchBarWhenScrolling = true
contentViewController.definesPresentationContext = true
searchController.searchBar.delegate = context.coordinator
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<SearchController>) {
//
}
}
extension SearchController {
func makeCoordinator() -> SearchController<Result>.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UISearchResultsUpdating, UISearchBarDelegate {
var parent: SearchController
init(_ parent: SearchController){self.parent = parent}
// MARK: - UISearchResultsUpdating
func updateSearchResults(for searchController: UISearchController) {
self.parent.searchText = searchController.searchBar.text!
}
// MARK: - UISearchBarDelegate
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.parent.searchText = ""
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
self.parent.searchText = ""
return true
}
}
}
// "nofity" the result content about the searchText
struct SearchResultView<Content: View>: View {
#Binding var searchText: String
private var content: (_ searchText:String)->Content
init(result searchText: Binding<String>, #ViewBuilder content: #escaping (_ searchText:String) -> Content) {
self._searchText = searchText
self.content = content
}
var body: some View {
content(searchText)
}
}
I don't if this is what you want, but thanks again!.
(EDIT) iOS 15:
iOS 15 added the new property .searchable(). You should probably use that instead.
Original:
I just made a package that would likely solve this issue. It's similar to #EthanMengoreo's answer, but has (in my opinion) a more SwiftUI-like syntax.
I'm also including the full relevant source code here for those who dislike links or just want to copy/paste.
Extension:
// Copyright © 2020 thislooksfun
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import SwiftUI
import Combine
public extension View {
public func navigationBarSearch(_ searchText: Binding<String>) -> some View {
return overlay(SearchBar(text: searchText).frame(width: 0, height: 0))
}
}
fileprivate struct SearchBar: UIViewControllerRepresentable {
#Binding
var text: String
init(text: Binding<String>) {
self._text = text
}
func makeUIViewController(context: Context) -> SearchBarWrapperController {
return SearchBarWrapperController()
}
func updateUIViewController(_ controller: SearchBarWrapperController, context: Context) {
controller.searchController = context.coordinator.searchController
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UISearchResultsUpdating {
#Binding
var text: String
let searchController: UISearchController
private var subscription: AnyCancellable?
init(text: Binding<String>) {
self._text = text
self.searchController = UISearchController(searchResultsController: nil)
super.init()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
searchController.obscuresBackgroundDuringPresentation = false
self.searchController.searchBar.text = self.text
self.subscription = self.text.publisher.sink { _ in
self.searchController.searchBar.text = self.text
}
}
deinit {
self.subscription?.cancel()
}
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else { return }
self.text = text
}
}
class SearchBarWrapperController: UIViewController {
var searchController: UISearchController? {
didSet {
self.parent?.navigationItem.searchController = searchController
}
}
override func viewWillAppear(_ animated: Bool) {
self.parent?.navigationItem.searchController = searchController
}
override func viewDidAppear(_ animated: Bool) {
self.parent?.navigationItem.searchController = searchController
}
}
}
Usage:
import SwiftlySearch
struct MRE: View {
let items: [String]
#State
var searchText = ""
var body: some View {
NavigationView {
List(items.filter { $0.localizedStandardContains(searchText) }) { item in
Text(item)
}.navigationBarSearch(self.$searchText)
}
}
}