How to add a SwiftUI object in UIkit - swift

I have already used Hosting View Controller with connecting my swiftui class to use SwiftUI in storyboard (UIkit) but the issue is I don't want to use it in another page I want to add SwiftUI variable (graphic) to the same page as I created with UIkit. How can I put some stuff in UIkit via SwiftUI, is there something like Container hosting View? there is Container View which helps you to add views in View but you can't edit that like editing Hosting View Controller. Please give me a hand.
I showed where I want the SwiftUI in image below 👇

if you want to use UIKit in SwiftUI, you make use of UIViewControllerRepresentable (A view that represents a UIKit view controller).
if you add UiViewControllerRepresentable Protocol.
you can UIViewController in SwiftUI.
struct ViewControllerRepresentation: UIViewControllerRepresentable {
}
this is Example
final class RedLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
textColor = .red
}
}
struct RepresentableRedLabel: UIViewRepresentable {
var text: String
let redLabel = RedLabel()
func makeUIView(context: Context) -> UILabel {
redLabel
}
func updateUIView(_ uiView: UILabel, context: Context) {
redLabel.text = text
}
}

Related

Admob Ad Inflates, but overlaps existing views in SwiftUI

I have this app that I am trying to use AdMob with that is made with SwiftUI. I had been using official AdMob documentation with SwiftUI to help me do this. I have one issue however, the ad successfully gets loaded, but the ad is located underneath another view, much like a ZStack without a ZStack present. No matter where I put it, it gets placed beneath a view. I have used the .frame() attribute, and it works using the BannerAd view, but then there is a giant white space there when the ad can't be loaded. Here are screenshots that may help solve the issue:
(Image on the left) This is what happens using the Google provided code (They say that the banner would reload into its own view when the ad is loaded, but does not appear to happen) As you can see, the add is below the name of the current herb.
(Image on the right)This is what happens when using the .frame() attribute before the ad is loaded. The ad loads correctly, but leaves this white space when not loaded or unable to load.
Here is the code to make the banner work with SwiftUI
import SwiftUI
import Foundation
import GoogleMobileAds
protocol BannerViewControllerWidthDelegate: AnyObject {
func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat)
}
class BannerViewController: UIViewController {
weak var delegate: BannerViewControllerWidthDelegate?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Tell the delegate the initial ad width.
delegate?.bannerViewController(self, didUpdate: view.frame.inset(by: view.safeAreaInsets).size.width)
}
override func viewWillTransition(
to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator
) {
coordinator.animate { _ in
// do nothing
} completion: { _ in
// Notify the delegate of ad width changes.
self.delegate?.bannerViewController(
self, didUpdate: self.view.frame.inset(by: self.view.safeAreaInsets).size.width)
}
}
}
struct BannerAd: UIViewControllerRepresentable {
#State private var viewWidth: CGFloat = .zero
private let bannerView = GADBannerView()
private let adUnitID = "ca-app-pub-3940256099942544/6300978111"
// Make the view to be used in SwiftUI
func makeUIViewController(context: Context) -> some UIViewController {
let bannerViewController = BannerViewController()
bannerView.adUnitID = adUnitID
bannerView.rootViewController = bannerViewController
bannerViewController.view.addSubview(bannerView)
bannerViewController.delegate = context.coordinator
return bannerViewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
if(viewWidth != .zero){
// Nothing happens
} else {
// Return when the size is greater than 0
//TODO: Fix !!!! This does not push other views down as it should, a frame could be set, but the ad does not push everything down or up
return
}
// Request a banner ad with the updated viewWidth.
bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
bannerView.load(GADRequest())
}
}
}
The code may be long, but you can ignore the coordinator as that is only for listeners, not nay real displaying of the advertisement.
What I want to happen exactly is for the Ad to move every object down so that when the ad is loaded, it will fit respectfully at the top of the page. I have looked at all sorts of sources, but I cannot find any solutions to this anywhere. Please help me, and thank you!

SwiftUI - Display view on UIWindow

I'm trying to display a custom SwiftUI view similar to a Toast in Android.
My issue is that I would like to display this particular view above everything else, using the current UIWindow.
Currently, while working on static func displayToastAboveAll() located in my ToastView, this is how far i got
public struct ToastView: View {
static func displayToastAboveAll() {
let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first // window
let viewToShow = ToastView(my params) // my view to display
// This part I'm not sure of
let hostingController = UIHostingController(rootView: viewToShow)
window?.addSubview(hostingController.view)
}
public var body: some View {
// MyDesign
}
}
Any idea how should I use the window to put the ToastView at its proper place, and still being able to navigate within the app (and use the outlets) while having the view displayed ?
I managed to do what I wanted.
Basically, this code is working, but I had to remove some constraints from my SwiftUI view and add them with UIKit using the static func.
Also, I had to pass by a modifier (see below) and put ToastView init in private.
public struct ToastModifier: ViewModifier {
public func body(content: Content) -> some View {
content
}
}
extension View {
public func toast() -> some View {
ToastView.displayToastAboveAll()
return modifier(ToastModifier())
}
}
This is done to force the use of either modifier (SwiftUI, by doing .toast, just like you'd do .alert) or directly by calling the static func ToastView.displayToastAboveAll() (UIKit).
Indeed, I dont wont this Toast to be a part of the view, I want to trigger it like an alert.
Finally, special warning because passing ToastView into UIHostingViewController will mess with some of the animations.
I had to rewrite animations in UIKit in order to have a nice swipe & fade animation.

Magnifier SwiftUI

I'm trying to render 2 SwiftUI views, in a VStack.
struct ContentView: View {
let content = // [...]
let magnifier = // [...]
var body: some View {
VStack {
self.content
self.magnifier
}
}
}
The first view (i.e., content) is a TextView, the second view (i.e., magnifier) is just a magnifier to the first view with a given scale and offset. This is basically the "zoom" functionality of some notes apps, Notability for example.
With UIKit I would have write a class Magnifier: UIView, and in its draw() function, something like
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// [...]
self.contentView.layer.render(in: context)
}
Can we do something like that with SwiftUI or do we have to use UIKit and UIViewRepresentable?

Where do I find spinner Icon in SwiftUI?

I learned that it is possible to render system images like so:
Image(nsImage: NSImage(imageLiteralResourceName: NSImage.addTemplateName))
But there seem to be no corresponding template name for spinner:
Do I have to use custom svg?
Xcode 12
it's just a simple view.
ProgressView()
Currently, it's defaulted to CircularProgressViewStyle but you can manually set the style of it by adding the following modifer:
.progressViewStyle(CircularProgressViewStyle())
Also, the style could be anything that conforms to ProgressViewStyle
Xcode 11
You can use NSProgressIndicator directly in SwiftUI:
Implementation
struct ProgressIndicator: NSViewRepresentable {
typealias TheNSView = NSProgressIndicator
var configuration = { (view: TheNSView) in }
func makeNSView(context: NSViewRepresentableContext<ProgressIndicator>) -> NSProgressIndicator {
TheNSView()
}
func updateNSView(_ nsView: NSProgressIndicator, context: NSViewRepresentableContext<ProgressIndicator>) {
configuration(nsView)
}
}
Sample Usage
var body: some View {
ProgressIndicator {
$0.controlTint = .blueControlTint
}
}
You can replace TheNSView with any other NSView you need to use in SwiftUI.
This icon cannot get as Image.
I have add my source code form my swift ui class:
ActivityIndicator

How to set color of NSView in Cocoa App using Swift?

I used to develop for iOS and I do not understand why I can't easily change background color of NSViews inside my main view.
Let's say I have a view controller with a main view in it. In this view I've added 3 custom views, I've set their constraints to fit the main view.
I've created 3 outlets to my view controller:
#IBOutlet weak var topView: NSView!
#IBOutlet weak var leftView: NSView!
#IBOutlet weak var rightView: NSView!
After that, I'm trying to set the background of these views and can't do this. I don't see any color changes when I run the app.
Here is the code I've added to show colors for my custom views:
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
self.topView.layer?.backgroundColor = NSColor.blue.cgColor
self.rightView.layer?.backgroundColor = NSColor.green.cgColor
self.leftView.layer?.backgroundColor = NSColor.yellow.cgColor
}
And it shows nothing (no background color on my views):
I can't understand why this code doesn't work. Why it is so hard to set the color of an NSView (while it is so easy in iOS development)? I'm using Xcode 8 and writing code using Swift 3.
You should set wantsLayer to true for your subviews, not for your superview.
self.topView.wantsLayer = true
self.rightView.wantsLayer = true
self.leftView.wantsLayer = true
The layer approach is convenient, but it will create a terrible issue unless you have a very dark background again a bright text color (or vice versa... I don't remember which is which any more.) for an NSTextField label. Unlike Cocoa Touch, which lets you change the background color of a UIView object, unfortunately, you have to subclass NSView in Cocoa for certainty like the following.
import Cocoa
class RedView: NSView {
override func draw(_ rect: NSRect) {
super.draw(rect)
let color = NSColor.red
color.set()
NSRectFill(rect)
}
}
Then set the class name according to whatever you call your subclass (RedView here) under the identity inspector of the interface builder. If you want to change the background color programatically, wire up the view object. Then do something like the following.
import Cocoa
class MyView: NSView {
// MARK: - Variables
var backgroundColor = NSColor()
// MARK: - Draw
override func draw(_ rect:NSRect) {
super.draw(rect)
backgroundColor.set()
NSRectFill(rect)
}
// MARK: - Method
func changeBackgroundColor(color: NSColor) {
backgroundColor = color
setNeedsDisplay(self.bounds)
}
}
You can then call changeBackgroundColor from the view controller like...
#IBOutlet weak var myView: MyView!
myView.changeBackgroundColor(color: NSColor.green)