Show SwiftUI Dialog box on top of UIKit View - swiftui

Newbie at SwiftUI here. I am trying to show a dialog built in SwiftUI on top of an existing UIKit View. The idea is to be able to see the content of the UIKit view behind the SwiftUI dialog (like the default behaviour of an alert dialog box). But no what I try, I am unable to see the contents of the UIKit view. Is this even achievable?
I want an alert style dialog with background opacity adjusted somehow to see the contents of the UIKit view. Here is my output:
alert content hides the view behind it
Can somebody please point me in the right direction.
Here is my code sample:
The dialog in SwiftUI:
struct TestDialog: View {
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.black.opacity(0.5))
.frame(maxHeight: .infinity)
VStack(alignment: .center, spacing: 15) {
Text(.init("Some Text"))
.multilineTextAlignment(.center)
.padding()
Button(action: {}) {
Text("Button 1")
.padding(10)
}
Button(action: {}) {
Text("Button 2")
.padding(10)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.white))
.padding(40)
}
}
}
and the method called in my viewDidLoad():
func showTestDialog() {
let testView = TestDialog()
let testchildView = UIHostingController(rootView: testView)
addChild(testchildView)
let titleBarOffset: CGFloat = 11
testchildView.view.frame = view.frame.offsetBy(dx: 0, dy: -titleBarOffset)
view.addSubview(testchildView.view)
testchildView.didMove(toParent: self)
}

In order to see through the SwiftUI, you need to make sure it's host view has a transparent background:
testchildView.view.backgroundColor = .clear
This must be done at the host view level, since it is the parent/container.

Related

Make SwiftUI view to expand to UIHostingController size

I have a SwiftUI view that needs to be shown in a UIKit View Controller. I'm using UIHostingController to embed my SwiftUI view.
I'm trying to figure out how can my SwifUI view expands its size to match UIHostingController's frame. My UIHostingController currently has the same frame as backgroundImageView of the the ViewController but my FeatureIntroductionView does not expand to fit in UIHostingController
This is my SwiftUI view
struct FeatureIntroductionView: View {
let image: String
let title: String
let subtitle: String
let buttonTitle: String
var body: some View {
VStack(spacing: 33) {
Image(image)
VStack(alignment: .center, spacing: 4) {
Text(title)
.foregroundColor(.white)
.font(Font(UIFont.boldFontWith(size: 28)))
Text(subtitle)
.foregroundColor(.white)
.font(Font(UIFont.regularFontWith(size: 16)))
.multilineTextAlignment(.center)
}
SecondaryButton(title: buttonTitle) {
// Close
}
}
.padding(48)
.background(.ultraThinMaterial,
in: Rectangle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
I'm embedding it like below in my UIViewController
func addIntroductoryView() {
let swipeFeatureIntroductionView = FeatureIntroductionView(image: "Swipe",
title: "Swipe to change",
subtitle: "Switch between quotes by swiping gesture or tapping on the screen.",
buttonTitle: "Got it")
var swipeFeatureIntroductionHostingController = UIHostingController(rootView: swipeFeatureIntroductionView)
swipeFeatureIntroductionHostingController.view.invalidateIntrinsicContentSize()
addChild(swipeFeatureIntroductionHostingController)
swipeFeatureIntroductionHostingController.view.frame = backgroundImageView.frame
swipeFeatureIntroductionHostingController.view.layer.cornerRadius = 16
swipeFeatureIntroductionHostingController.view?.clipsToBounds = true
swipeFeatureIntroductionHostingController.view.backgroundColor = .clear
view.addSubview(swipeFeatureIntroductionHostingController.view)
swipeFeatureIntroductionHostingController.didMove(toParent: self)
}
I'm looking for a way to expand FeatureIntroductionView to fill the same space as backgrodun image.
Thanks
You’re applying your .frame after you apply your .background.
This means that the background view is applied to the area of the view as determined by its contents, and when the view’s frame is expanded the background stays the same size.
Try switching the order of the modifiers so that the frame size is readjusted before the background is applied.
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.ultraThinMaterial, in: Rectangle())

SwiftUI Is there any built in view that kind of slides in from the side and takes up 3/4 of the screen?

This is probably a custom view but in the Reddit app there's a toolbar and the top left button(3 lines) opens this kind of view from the side that moves the current view to the right so you can only see about 25% of it and a new view that takes up about 75% of the screen slides in. Is there anything like this built into SwiftUI and if there isn't how would I go about implementing something like this?
This is my custom side bar behave similarly to what you just mentioned, you can try it. (Images and Code are below)
Before click:
After clicked:
struct ContentView: View {
#State var isClicked = false
var body: some View {
HStack {
Rectangle()
.fill(.orange)
.frame(width: isClicked ? UIScreen.main.bounds.width * 0.75 : 0)
VStack {
HStack {
Button {
withAnimation {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
}
}
}

Customize navigationBar in Swift UI

I am new in swift ui. I want to put image button on the side of the NavigationBar title.
I want to be able to click the user image and navigate to another view. How?
You need to use navigationBarItems for putting image to navigation bar and you should add NavigationLink to that image. For center the title you need to set navigation bar title's displayMode to .inline or you can use new Toolbar api
struct ContentView: View {
var body: some View {
NavigationView {
Text("Welcome to Stack Overflow")
.navigationBarTitle("Header", displayMode: .inline)
.navigationBarItems(leading: NavigationLink(destination: Text("Destination")) {
Image(systemName: "person.crop.circle.fill")
.font(.title)
})
}
}
}
Screenshot
Another way using toolbar item.
I am adding TapGesture on image icon, and keeping it out of navigation link, as the image is not getting circular inside the NavigationLink in ToolbarItemGroup.
By leveraging isActive property of NavigationLink which monitors onTap state we can determine either we want to push our view or not.
import SwiftUI
struct WeatherView: View {
#State var onTap = false
var borderColor: Color = Color("Black")
var addProjectToolbarItem: some ToolbarContent {
ToolbarItemGroup(placement: .navigationBarLeading) {
NavigationLink(destination:Text("Welcome"), isActive: self.$onTap) {
EmptyView()
}
Image("yourImage")
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
.onTapGesture {
onTap.toggle()
}
}
}
var body: some View {
NavigationView {
VStack {
Text("First view")
}
.toolbar{
addProjectToolbarItem
}
.navigationTitle("Header")
.navigationBarTitleDisplayMode(.inline)
}
}
}

SwiftUI: Shadow glitch after context menu

I have a rectangle with a shadow and a context menu. When I close this context menu the shadow of the rectangle appears with a delay (~0.5 seconds). Both the shadow of the complete rectangle as well the shadow of the inner elements. I'm not sure what I am doing wrong.
struct Playground: View {
var body: some View {
VStack(alignment: .leading, spacing: 4.0) {
Spacer()
HStack {
Spacer()
Image(systemName: "house")
.font(.system(size: 50))
Spacer()
}
Text("SwiftUI for iOS 14").fontWeight(.bold).foregroundColor(Color.white)
Text("20 Sections").font(.footnote).foregroundColor(Color.white)
}
.frame(width: 200, height: 300)
.padding(.all)
.background(Color.blue)
.cornerRadius(20.0)
.shadow(radius: 10)
.contentShape(RoundedRectangle(cornerRadius: 20))
.contextMenu(menuItems: {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
})
}
}
I found a hacky way to mitigate the issue. This is using pure UIKit and Objective-C but it might be able to be done in SwiftUI too in one way or another.
This is a temporary direct-via-subview access to the view that animates, and I simply attach a shadow to it. This way the animating view will have a shadow and when the REAL shadow pops in, it does so below this shadow. The result looks fine.
-(void)contextMenuInteraction:(UIContextMenuInteraction *)interaction
willEndForConfiguration:(UIContextMenuConfiguration *)configuration
animator:(id<UIContextMenuInteractionAnimating>)animator {
// TODO: Find view dynamically /safer
UIView *platter = (UIView*)self.view.window.subviews[1].subviews[1].subviews[0];
platter.clipsToBounds = NO;
platter.layer.shadowOffset = CGSizeMake(0, 0);
platter.layer.shadowRadius = 5;
platter.layer.shadowOpacity = 0.5;
platter.layer.shadowColor = [UIColor blackColor].CGColor;
}
This works in iOS 13. Have not tried on iOS 14.

Semi-transparent (blurry like VisualEffectView) of the view behind the current view

In SwiftUI the view behind the tab bar in a TabView will shine through as if the backside of the tab bar was frosted glass. Apple uses this look all over the place in their own apps. But how do I add it to a view in SwiftUI?
Here's an example from the Podcasts app. The tab bar has the frosted glass effect. And so does the overlay mini player on top of the tab bar. Any tab bar in a TabView in will have this look by default, but not an associated overlay (the mini player in this case).
The Apple way
Investigating on the view hierarchy shows that Apple is using UIKit and UIVisualEffectViewfor this reason. You can define a VisualEffectView with just 5 lines of code:
struct VisualEffectView: UIViewRepresentable {
var effect: UIVisualEffect?
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) { uiView.effect = effect }
}
Usage Example:
struct ContentView: View {
var body: some View {
ZStack {
Image("BG")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
VisualEffectView(effect: UIBlurEffect(style: .dark))
.edgesIgnoringSafeArea(.all)
Text("Hello \nVisual Effect View")
.font(.largeTitle)
.fontWeight(.black)
.foregroundColor(.white)
}
}
}
The Native SwiftUI way:
You can add .blur() modifier on anything you need to be blurry like:
struct ContentView: View {
var body: some View {
ZStack {
Image("BG")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
.blur(radius: 20) // <- this is the important modifier. The rest is just for demo
Text("Hello \nSwiftUI Blur Effect")
.font(.largeTitle)
.fontWeight(.black)
.foregroundColor(.white)
}
}
}
Note the top and bottom of the view
Note that you can Group multiple views and blur them together.
iOS 15 - Apple Material
You can use iOS predefined materials with one line code:
.background(.ultraThinMaterial)