Inserting UIKit content into a SwiftUI view hierarchy - ios

I'm sorry if the title is confusing, i'm kind of new to the whole thingy.
I'm trying to integrate PassBase ID verification to my app, which is built using SwiftUI, their documentation offers instructions using Swift and view Controllers.
My question is, is there a way to insert the Swift code part into my SwiftUI view?
The code example from their Documentation:
import Passbase
import UIKit
class ViewController: UIViewController, PassbaseDelegate {
override func viewDidLoad() {
super.viewDidLoad()
PassbaseSDK.delegate = self
// Optional - You can prefill the email to skip that step.
Passbase.prefillUserEmail = "testuser#yourproject.com"
let button = PassbaseButton(frame: CGRect(x: 40, y: 90, width: 300, height: 60))
self.view.addSubview(button)
}
func onFinish(identityAccessKey: String) {
print("onFinish with identityAccessKey \(identityAccessKey)")
}
func onSubmitted(identityAccessKey: String) {
print("onSubmitted with identityAccessKey \(identityAccessKey)")
}
func onError(errorCode: String) {
print("onError with code \(errorCode)")
}
func onStart() {
print("onStart")
}
}
As i understand this part of code should create a button in a VC.
My goal is to add this button with functionality to my SwiftUI view.
Full Documentation: https://docs.passbase.com/ios#general
Thank you all in advance for the help!

The basic strategy is to use a View that represents the content you want to bring in from a UIViewController. Your View is going to conform to UIViewCotrollerRepresentable and use the functions of that protocol to create and manage the UIKit content.
The UIViewControllerRepresentable documentation is here
And, as was commented on your original post by vadian, there is A tutorial with sample code
With the sample code above, I would rename "ViewController" to be something like PassBaseViewController or PBViewController, then you would create a View that derives from UIViewControllerRepresentable
You end up with a file called PBViewController.swift that has your code from above:
import Passbase
import UIKit
class PBViewController: UIViewController, PassbaseDelegate {
override func viewDidLoad() {
super.viewDidLoad()
PassbaseSDK.delegate = self
// Optional - You can prefill the email to skip that step.
Passbase.prefillUserEmail = "testuser#yourproject.com"
let button = PassbaseButton(frame: CGRect(x: 40, y: 90, width: 300, height: 60))
self.view.addSubview(button)
}
... and the rest of the code from your question here ...
Then (probably in another file, but not necessarily) you could create the SwiftUIView that uses that view controller:
struct PassBaseView : UIViewControllerRepresentable {
typealias UIViewControllerType = PBViewController
func makeUIViewController(context: Context) -> PBViewController {
return PBViewController()
}
func updateUIViewController(_ uiViewController: PBViewController, context: Context) {
/* code here to make changes to the view controller if necessary when this view is updated*/
}
}

Related

UIKit ViewController ===> into SwiftUI ContentView

Trying to integrate Facebook audience Network ad placements into my SwiftUI project.
The tutorial published by Facebook assumes the use of UIKit only!
With zero experience in UIKit, need your kind help to enable me to integrate the follwing lines of code into my ContentView as a SwiftUI view:
//Add a UIView element to the main View element and name it to adContainer.
//Now, in your View Controller header file (or Swift file, if you are a Swift user),
//import FBAudienceNetwork, declare conformance to the FBAdViewDelegate protocol,
//and add an instance variable for the ad unit
import UIKit
import FBAudienceNetwork
class ViewController: UIViewController, FBAdViewDelegate {
#IBOutlet private var adContainer: UIView!
private var adView: FBAdView?
}
//Add the code below to viewDidLoad; Create a new instance of FBAdView and add it to the view.
//FBAdView is a subclass of UIView. You can add it to your view hierarchy just like any other view.
override func viewDidLoad() {
super.viewDidLoad()
// Instantiate an AdView object.
// NOTE: the placement ID will eventually identify this as your app, you can ignore while you
// are testing and replace it later when you have signed up.
// While you are using this temporary code you will only get test ads and if you release
// your code like this to the App Store your users will not receive ads (you will get a 'No Fill' error).
let adView = FBAdView(placementID: "YOUR_PLACEMENT_ID", adSize: kFBAdSizeHeight50Banner, rootViewController: self)
adView.frame = CGRect(x: 0, y: 0, width: 320, height: 250)
adView.delegate = self
adView.loadAd()
self.adView = adView
}
//Optionally, you can add the following functions to handle the cases
//where the ad is closed or when the user clicks on it:
func adViewDidClick(_ adView: FBAdView) {
print("Ad was clicked.")
}
func adViewDidFinishHandlingClick(_ adView: FBAdView) {
print("Ad did finish click handling.")
}
func adViewWillLogImpression(_ adView: FBAdView) {
print("Ad impression is being captured.")
}
//Add and implement the following two delegate functions in your
//View Controller to handle ad loading failures:
func adView(_ adView: FBAdView, didFailWithError error: Error) {
print("Ad failed to load with error: \(error.localizedDescription)")
}
func adViewDidLoad(_ adView: FBAdView) {
print("Ad was loaded and ready to be displayed")
showAd()
}
private func showAd() {
guard let adView = adView, adView.isAdValid else {
return
}
adContainer.addSubview(adView)
}
Your help would be tremendously appreciated would be

Subclassing UIView from Kotlin Native

UIKit is designed to be used through subclasses and overridden methods.
Typically, the drawRect objective-C method of UIView is implemented like this in SWIFT:
import UIKit
import Foundation
class SmileView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
let smile = ":)" as NSString
smile.draw(in: rect, withAttributes: nil)
}
}
Unfortunately, the UIKit import in Kotlin defines these functions as extensions function that cannot be overridden.
Did anybody succeed in subclassing an UIView from Kotlin through a custom cinterop configuration?
So we managed to make it work.
1. Add a cinterop configuration task in the build.gradle.kts
kotlin {
android()
ios {
binaries {
framework {
baseName = "shared"
}
}
compilations.getByName("main") {
val uikit by cinterops.creating {
}
}
}
2. Add a `src/nativeinterop/cinterop/uikit.def` file.
package = demo.cinterop
language = Objective-C
---
#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>
#protocol UIViewWithOverrides
- (void) drawRect:(CGRect)aRect;
- (void) layoutSubviews;
#end
3. Create a custom UIView class
The class extends the UIView from UIKit and implements the previously created UIViewWithOverridesProtocol (the suffix is automatically added)
package demo
import demo.cinterop.UIViewWithOverridesProtocol
import kotlinx.cinterop.*
import platform.CoreGraphics.*
import platform.UIKit.*
#ExportObjCClass
class MyView() : UIView(frame = CGRectMake(.0, .0, .0, .0)), UIViewWithOverridesProtocol {
override fun layoutSubviews() {
println("layoutSubviews")
setNeedsDisplay()
}
override fun drawRect(aRect: CValue<CGRect>) {
val rectAsString = aRect.useContents {
"" + this.origin.x + ", " + this.origin.y + ", " + (this.origin.x +this.size.width) + ", " + (this.origin.y +this.size.height)
}
println("drawRect:: Rect[$rectAsString]")
val context: CPointer<CGContext>? = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
val components = cValuesOf(0.0, 0.0, 1.0, 1.0)
CGContextSetFillColor(context, components)
val square = CGRectMake(100.0, 100.0, 200.0, 200.0)
CGContextFillRect(context, square)
}
}
fun createMyView(): UIView = MyView()
4. Use it from Swift
struct ChartView: View {
var body: some View {
VStack {
Text("Chart View")
MyView()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
}
struct ChartView_Previews: PreviewProvider {
static var previews: some View {
ChartView()
}
}
struct MyView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
UIChartViewKt.createMyView()
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
The above answer is awesome, and it served me pretty well until I needed to override updateConstraints() - which has to call super.updateConstraints(). Without that, I was getting runtime errors, and I found no way how to do that call via the Kotlin <-> Swift interop (and now I'm reasonably sure it's really not possible).
So instead, I gave up on trying to subclass the custom UIView in Swift, and only focused on actually instantiating it from Kotlin/Native (so that it is easy to pass it the data it needs):
class CustomView : UIView {
/* Data we need to use from the Kotlin Code */
lazy var kotlinClass: KotlinClass? = nil
... init etc. ...
override func updateConstraints() {
... my stuff ...
super.updateConstraints()
}
override func draw(_ rect: CGRect) {
... call the kotlinClass' methods as you need ...
}
}
And implemented a factory function to instantiate it:
func customViewFactory(kotlinClass: KotlinClass) -> UIView {
return CustomView(kotlinClass: kotlinClass)
}
Then early during the app startup, I pass this factory function to the Kotlin/Native code like this:
KotlinClass.Companion.shared.setCustomViewFactory(factory: customViewFactory(kotlinClass:))
In the Kotlin part of the project (that is actually compiled before the Swift part), it looks like this:
class KotlinClass {
companion object {
/* To be used where I want to instantiate the custom UIView from the Kotlin code. */
lateinit var customViewFactory: (kotlinClass: KotlinClass) -> UIView
/* To be used early during the startup of the app from the Swift code. */
fun setCustomViewFactory(factory: (kotlinClass: KotlinClass) -> UIView) {
customViewFactory = factory
}
}
When I want to instantiate the custom UIView in the Kotlin code, I just call:
val customView = customViewFactory(this)
And then I can work with this customView as I need in the Kotlin part, even though the Kotlin part is compiled first.

Add imageview to all viewcontrollers programmatically

there is a requirement like add an imageview to all viewcontrollers but I have 150+ xib's and it is time consuming to put imageview in every single xib.
Is there a common way to do it? I googled but nothing useful found.
Any help would be appreciated.
It will be easy if you use base class like this
class BaseVC: UIViewController
{
var imageView:UIImageView = UIImageView.init()
override func viewDidLoad() {
super.viewDidLoad()
addImageView()
}
override func viewWillAppear(_ animated: Bool) {
//self.view.bringSubview(toFront: imageView) //To bring imageview infront of other views put this method as per your requirement
}
func addImageView(name:String = "default")
{
let image = UIImage(named: name)
imageView.image = image
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 200)
//view.addSubview(imageView)
view.insertSubview(imageView, at: 0) /*For put image view below all image*/
}
}
You need to derive all your view controller from this like this
class YourVC: BaseVC
also you can change the image with different viewcontrollers.
like
class YourVC: BaseVC
{
override func viewDidLoad() {
super.viewDidLoad()
addImageView(name:"xyz")
}
}
you can write an extension for UIView to add imageview into it and run it into every your viewcontrollers.
extension UIView {
func addImage() {
let imageView = UIImageView(frame: frame)
imageView.image = UIImage(named: "Your Image Name")
addSubview(imageView)
imageView.didMoveToSuperview()
}
}
Create New ViewController without storyboard.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setImageView()
}
func setImageView() {
let thisImageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 70, height: 70))
thisImageView.image = UIImage(named: "your image name")
view.addSubview(thisImageView)
}
}
now you can set this BaseViewController as delegate of your project's ViewControllers.
import UIKit
class MainViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
now you have this ImageView in MainViewController and all ViewControllers have BaseViewContoller as delegate.
Hope to be useful. Also sorry about my English.
TL;DR No. Based on my understanding of what you are imagining, no you can't write a function to add a UIImageView to every one of your viewControllers.
Long answer:
You need to create a separate controller swift file for each View and set it up in that file. You could create a supporting file in which you setup up the the ImageView then call in in the viewDidLoad for each view controller. (You could even make this an extension of UIView so you can just call something like self.setUpImageView())
My personal recommendation would be to drop the xibs as soon as you can and recreate everything pragmatically, I know it's a headache to throw all your work away but it is really worth it in the end on top of just being good practice. I have a file that I found that makes autolayout a breeze that I can share with you if you'd like. I used to really enjoy storyboards and xibs myself but they are a hassle that just isn't worth it anymore and cause such a headache in situation like this.

how to force run a method in all viewControllers viewDidLayoutSubviews functions automatically?

say I have this extension that helps me in changing the font size in all text elements of a UIViewController
extension UIView {
func changeFontSize(){
let fontSize = CGFloat(5)
if let v = self as? UIButton {
v.titleLabel?.font = v.titleLabel?.font.withSize(fontSize)
print("didChangeFontSizeFor_Button")
} else if let v = self as? UILabel {
v.font = v.font.withSize(fontSize)
} else if let v = self as? UITextField {
v.font = v.font?.withSize(fontSize)
} else {
for v in subviews {
v.changeFontSize()
}
}
}
}
it works fine when I call it like this
override func viewDidLayoutSubviews() {
view.changeFontSize()
}
now the question, is there a way where I can make this more dynamic to be forced in all viewControllers?
say we have 3 view Controllers, and I want to make some CocoaPods library where people just make a simple call like this
forceAppFontSize.fontSize = CGFloat(15)
to change the font size for all other screens..
class 1
import UIKit
class v1: UIViewController{
override func viewDidLayoutSubviews() {
print("someCommand1")
print("someCommand2")
}
}
class 2
import UIKit
class v2: UIViewController{
override func viewDidLayoutSubviews() {
print("someCommand")
}
}
class 3
import UIKit
class v3: UIViewController{
}
is there a way to make this dynamic without breaking the original viewDidLayoutSubviews ? see class 1 for example, the view has some commands already that to be not destroyed or replaced.
There is a much simpler way. Your actual goal is to update all views in your app. There is no need to go through each view controller. Simply call your changeFontSize() extension method on your app's main window.

How to make UI elements in class out of View Controller

I'm making app and I'm trying to make a View which contains a Label with a question. I want this view in my app and because I will use it repeatedly, I made a class (If I want to make some change, I can do It from one place). The UIView is called questionView (var questionView = UIView()). Problem is when I want to make questionView a subview of view. The error says that I don't have have "view" which I understand. I don't have view but how can I get it? Thank you
This is what is inside my Question class:
import Foundation
import UIKit
class Question {
// PROPERTIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
// ... next code, also not important
}
UPDATE:
There is my solution. It works BUT I think that it's not correct from a programming standpoint. Can anybody tell me anything about it? Thank you
My class in separate swift file:
My class in separate swift file:
class LabelClass {
var view = UIView()
init (view: UIView) {
self.view = view
}
var lbl = UILabel()
var lblView = UIView()
func makeLabel () {
self.lbl.frame = CGRectMake(0, 0, 150, 50)
self.lbl.text = "Text text text"
self.lbl.numberOfLines = 0
self.lblView.frame = CGRectMake(20, 20, 150, 50)
self.lblView.addSubview(self.lbl)
self.view.addSubview(lblView)
}
}
Piece of code my ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Added code:
var object = LabelClass(view: self.view)
object.makeLabel()
}
I don't know Swift, but as far as I know, only instances of UIViewController have a view property, the class Question does not, so you cannot add subviews to it.
What you probably want is making a subclass of UIView which contains a question label, or to add the questionLabel as a subview of questionView.
It is because you are trying to add your view to a normal Swift class which doesn't have a self.view instance. Your Question class must be a subclass of UIViewController cocoa class that it has a self.view instance and override methods.
class Question:UIViewController {
// PROPHERITIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
override func viewDidLoad() {
createQuestion("foo")
}
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// ... next code, also not important
}

Resources