I am using Main.StoryBoard with UIViewController, Now i have added SwiftUIView Inside This at top of the Storyboard as
The Code of View Controller is
import UIKit
import SwiftUI
extension UIView {
func addConstrained(subview: UIView) {
subview.translatesAutoresizingMaskIntoConstraints = false
subview.topAnchor.constraint(equalTo: topAnchor).isActive = true
subview.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
subview.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
subview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
class ViewController: UIViewController {
#IBOutlet weak var topNavView: UIView!
let childView = UIHostingController(rootView: TempView())
override func viewDidLoad() {
super.viewDidLoad()
childView.view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 152)
addChild(childView)
topNavView.addSubview(childView.view)
childView.didMove(toParent: self)
topNavView.addConstrained(subview: childView.view)
childView.view.isUserInteractionEnabled = true
}
}
The swiftUI View is---
import SwiftUI
struct TempView: View {
var body: some View {
ZStack {
Color.green
VStack {
Button {
print("clicked 1")
} label: {
Text("Button1")
}
.frame(width: 70, height: 60, alignment: .center)
Button {
print("clicked 2")
} label: {
Text("Button2")
}
.frame(width: 70, height: 60, alignment: .center)
}
}
//.background(.red)
.frame(height:100)
}
}
struct TempView_Previews: PreviewProvider {
static var previews: some View {
TempView()
}
}
When i click on Button1 & Button2 I am not able to click. but when i rotate device (Landscape ) it's working fine.. what could be the issue ?
Related
I am hosting a SwiftUI view inside a view controller. My SwiftUI view has a #Binding data type.
While setting the content view, I need to initialize and set a binding variable.
My SwiftUI View:
struct STSChooseItemsView: View {
#Environment(\.presentationMode) var presentationMode
#Binding var selectedImage : UIImage
var body: some View {
VStack{
HStack(){
Spacer()
.frame(width: 5)
Button(action: {
presentationMode.wrappedValue.dismiss()
self.selectedImage = UIImage()
}, label: {
Text("X")
.fontWeight(.heavy)
.foregroundColor(Color.black)
})
Spacer()
.frame(width: 100)
Text("CHOOSE ITEMS")
.font(.headline)
.padding()
Spacer()
.frame(width: 100)
}
.frame(height: 40)
Spacer()
Image(uiImage: selectedImage)
.frame(width: UIScreen.main.bounds.width - 1, height: 300)
Spacer()
}
}
My view controller:
import UIKit
import SwiftUI
class STSChooseItemsViewController : UIViewController {
let contentView = UIHostingController(rootView: STSChooseItemsView(selectedImage: **(Please let me know what and how to put data here)**))
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "CHOOSE ITEMS"
addChild(contentView)
view.addSubview(contentView.view)
setupConstraints()
// Do any additional setup after loading the view.
}
fileprivate func setupConstraints(){
contentView.view.translatesAutoresizingMaskIntoConstraints = false
contentView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
I tried initializing the dummy image but I got an error: Cannot use instance member '$myImage' within property initializer; property initializers run before 'self' is available
Try creating a Binding using the init(get:set:) initializer.
class STSChooseItemsViewController: UIViewController {
var image = UIImage()
lazy var selectedImageBinding = Binding { /// I omitted the argument label using trailing closure shorthand
return self.image
} set: { newValue in
self.image = newValue /// `selectedImageBinding` must be a lazy var to access `self`
}
lazy var contentView = UIHostingController(rootView: STSChooseItemsView(selectedImage: selectedImageBinding) /// pass in the binding here
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "CHOOSE ITEMS"
addChild(contentView)
view.addSubview(contentView.view)
setupConstraints()
// Do any additional setup after loading the view.
}
fileprivate func setupConstraints() {
contentView.view.translatesAutoresizingMaskIntoConstraints = false
contentView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
I'm trying to show the iPad Popover share sheet (which uses UIPopoverPresentationController) from a SwiftUI Button. I've been able to get it to show but it only shows in the top left origin despite the sourceView and sourceRect being set differently.
Is there something else I need to set?
This is what the code currently produces,
This is the code I have so far,
struct ContentView: View {
#State
var label = "N/A"
#State
var buttonPos = CGRect(x: 0, y: 0,
width: 0, height: 0)
var body: some View {
VStack{
Spacer(minLength: 900)
GeometryReader { geometry in
Button("Share"){
print("Share button is located at \(buttonPos)")
showShareSheetAtPopover()
}.padding()
.frame(width: geometry.size.width)
.frame(height: geometry.size.height)
.background(Color.pink)
.onAppear(perform: {
buttonPos = geometry.frame(in: .global)
let localPos = geometry.frame(in: .local)
label = "Width: \(buttonPos.size) \n Global Pos: \(buttonPos.origin) \n Local Pos: \(localPos.origin)"
})
}.background(Color.orange)
Text(label)
.padding()
.background(Color.mint)
.frame(minHeight: 0, maxHeight: .infinity)
// don't truncate text
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}.background(Color.yellow)
}
func showShareSheetAtPopover(){
let vc = (UIApplication.shared.windows.first?.rootViewController)!
let shareItem = ["Some Text to Share"]
let ac = UIActivityViewController(
activityItems: shareItem,
applicationActivities: nil)
let onAnIPad = (ac.popoverPresentationController != nil)
print( onAnIPad ? "On an iPad":"Not on an iPad" )
if (onAnIPad){
let popoverPos = buttonPos
let ppc = ac.popoverPresentationController!
let sourceView = UIView(frame: popoverPos)
print("Source View Frame \(sourceView.frame)")
ppc.sourceView = sourceView
ppc.sourceRect = popoverPos
//vc.show(ac, sender: {})
vc.present(ac, animated: true)
print(ppc.frameOfPresentedViewInContainerView)
}else{
vc.show(ac, sender: {})
}
}
}
I got it kind of working by using a UIViewRepresentable to wrap a UIButton in the SwiftUI view and use that as the anchor point.
Here is the updated code, it is not at all pretty or clean.
import Foundation
import SwiftUI
final class MyUIButton: NSObject, UIViewRepresentable{
#objc let action: () -> Void
#objc var button: UIButton
init(action: #escaping () -> Void){
print("Creating button")
self.action = action
self.button = UIButton()
super.init()
self.button.setTitle("My Button", for: UIControl.State.normal)
self.button.configuration = .filled()
self.button.addTarget(self,
action: #selector(self.onButtonClick),
for: UIControl.Event.touchDown)
}
func makeUIView(context: Context) -> some UIView {
print("Making button")
return button
}
#objc func onButtonClick(){
print("onClick")
action()
}
func updateUIView(_ uiView: UIViewType, context: Context) {
print("Updating")
}
}
import SwiftUI
struct ContentView: View {
#State
var label = "N/A"
#State
var buttonPos = CGRect(x: 0, y: 0,
width: 0, height: 0)
// need to set it as a class variable otherwise the
// selector does not get called
// see https://stackoverflow.com/questions/45158000/uibutton-addtarget-selector-is-not-working
var myButton = MyUIButton(action:{
print("Clicked!")
})
var body: some View {
VStack{
Spacer(minLength: 900)
self.myButton
GeometryReader { geometry in
Button("Share"){
print("Share button is located at \(buttonPos)")
showShareSheetAtPopover()
}.padding()
.frame(width: geometry.size.width)
.frame(height: 40)
.background(Color.pink)
.onAppear(perform: {
buttonPos = geometry.frame(in: .global)
let localPos = geometry.frame(in: .local)
label = "Width: \(buttonPos.size) \n Global Pos: \(buttonPos.origin) \n Local Pos: \(localPos.origin)"
})
}.background(Color.orange)
Spacer(minLength: 50)
Text(label)
.padding()
.background(Color.mint)
.frame(minHeight: 0, maxHeight: .infinity)
// don't truncate text
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}.background(Color.yellow)
}
func showShareSheetAtPopover(){
let vc = (UIApplication.shared.windows.first?.rootViewController)!
let shareItem = ["Some Text to Share"]
let ac = UIActivityViewController(
activityItems: shareItem,
applicationActivities: nil)
let onAnIPad = (ac.popoverPresentationController != nil)
print( onAnIPad ? "On an iPad":"Not on an iPad" )
if (onAnIPad){
let popoverPos = buttonPos
let ppc = ac.popoverPresentationController!
let sourceView = UIView(frame: popoverPos)
print("Source View Frame \(sourceView.frame)")
ppc.sourceView = myButton.button
//ppc.sourceRect = popoverPos
//vc.show(ac, sender: {})
vc.present(ac, animated: true)
print(ppc.frameOfPresentedViewInContainerView)
}else{
vc.show(ac, sender: {})
}
}
}
firstly I am really new to iOS development and Swift (2 weeks coming here from PHP :))
I am trying to build a iOS application that has a side menu. And my intention is when I click on a item in the menu the new view will appear on screen like the 'HomeViewController' and for each consequent item like example1, 2 etc (In place of the menu button, Note I will be adding the top nav bar soon to open the menu)
I am wondering how I can accomplish this feature?
Thanks
ContentView.swift
import SwiftUI
struct MenuItem: Identifiable {
var id = UUID()
let text: String
}
func controllView(clickedview:String) {
print(clickedview)
}
struct MenuContent: View{
let items: [MenuItem] = [
MenuItem(text: "Home"),
MenuItem(text: "Example1"),
MenuItem(text: "Example2"),
MenuItem(text: "Example3")
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) {items in
HStack {
Text(items.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(/*#START_MENU_TOKEN#*/.leading/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
controllView(clickedview: items.text)
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent()
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State var menuOpen = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.background(Color(.systemBlue))
})
}
SideMenu(width: UIScreen.main.bounds.width/1.6, menuOpen: menuOpen, toggleMenu: toggleMenu)
}
.edgesIgnoringSafeArea(.all)
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The on tap command from the above code:
.onTapGesture {
controllView(clickedview: items.text)
}
HomeViewController.swift
import UIKit
import WebKit
class HomeViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://developer.apple.com")!
webView.load(URLRequest(url: url))
}
}
You'll need a couple of ingredients:
A way to store the state of the currently active view
A way to communicate the state between your menu and main content view
For the first one, I made an enum that listed the different types of views (ViewType) and added it to your MenuItem model.
For the second, you can pass state via a #Binding from parent to child views and back up the chain.
struct MenuItem: Identifiable {
var id = UUID()
let text: String
let viewType : ViewType
}
enum ViewType {
case home, example1, example2, example3
}
struct MenuContent: View{
#Binding var activeView : ViewType
let items: [MenuItem] = [
MenuItem(text: "Home", viewType: .home),
MenuItem(text: "Example1", viewType: .example1),
MenuItem(text: "Example2", viewType: .example2),
MenuItem(text: "Example3", viewType: .example3)
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) { item in
HStack {
Text(item.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(.leading)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
activeView = item.viewType
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
#Binding var activeView : ViewType
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent(activeView: $activeView)
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State private var menuOpen = false
#State private var activeView : ViewType = .home
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
VStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: .center)
.background(Color(.systemBlue))
})
}
switch activeView {
case .home:
HomeViewControllerRepresented()
case .example1:
Text("Example1")
case .example2:
Text("Example2")
case .example3:
Text("Example3")
}
}
SideMenu(width: UIScreen.main.bounds.width/1.6,
menuOpen: menuOpen,
toggleMenu: toggleMenu,
activeView: $activeView)
.edgesIgnoringSafeArea(.all)
}
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct HomeViewControllerRepresented : UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> HomeViewController {
HomeViewController()
}
func updateUIViewController(_ uiViewController: HomeViewController, context: Context) {
}
}
I have a TabView with six pages. The animation is in the last page.
When I attend to the last page the animation shows for a split second and disappear completely.
Thought it might be problem with the animation but it works elsewhere just fine.
I present this TabView using sheet.
Last page:
struct SixScreen: View{
#EnvironmentObject var session: SessionStore
#Binding var dismiss: Bool
var body: some View{
VStack(spacing: 16){
Spacer()
LottieView(name: "complete")
.frame(width: 200, height: 200, alignment: .center)
Button(action: {
dismiss.toggle()
}, label: {
Text("Start")
.frame(width: 100, height: 50, alignment: .center)
.foregroundColor(.blue)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .blue, radius: 5, x: 0, y: 1)
})
.padding(.bottom, 32)
Spacer()
}
}
}
Lottie View implementation:
struct LottieView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
var name: String!
var animationView = AnimationView()
class Coordinator: NSObject {
var parent: LottieView
init(_ animationView: LottieView) {
self.parent = animationView
super.init()
}
}
func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView()
animationView.animation = Animation.named(name)
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
animationView.heightAnchor.constraint(equalTo: view.heightAnchor)
])
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
animationView.play()
}
}
Tab View:
Group{
TabView{
FirstScreen()
SecondScreen()
ThirdScreen()
FourthScreen()
FifthScreen()
SixScreen(dismiss: $dismiss)
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.padding(.bottom)
}
.background(gradient)
.edgesIgnoringSafeArea(.all)
.navigationBarHidden(true)
}
Try to force refresh the view using the .id() modifier
struct AnimationView: View {
#State private var lottieID = UUID()
var body: some View {
LottieView(name: "complete")
.frame(width: 200, height: 200, alignment: .center)
.id(lottieID)
.onAppear {
lottieID = UUID()
}
}
}
After a lot of research and testing, if you're looking to keep the Lottie animation live inside the SwiftUI TabView you should add this code snippet:
public func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView(frame: .zero)
let animation = Animation.named(lottieFile)
animationView.animation = animation
animationView.animationSpeed = animationSpeed
animationView.contentMode = .scaleAspectFit
animationView.loopMode = loopMode
animationView.backgroundBehavior = .pauseAndRestore <------
I have tried to simulate how displaying the call view on the top of all. But I have some unclear points:
Is there any way to make it more simple?
Will the view hierarchy protected for that cases?
AppView.swift
import SwiftUI
struct AppView: View {
#ObservedObject var callVM: CallViewModel
init() {
self.callVM = CallViewModel()
}
var body: some View {
VStack {
IncomingCallView(rootView: appView, isActive: self.$callVM.isIncomingCallActive)
TabView {
TabOne()
.tabItem {
Image(systemName: "list.dash")
Text("Menu")
}
TabTwo()
.tabItem {
Image(systemName: "square.and.pencil")
Text("Order")
}
}
}
.onAppear(perform: load)
}
var appView: some View {
Text("")
}
func load() {
self.callVM.getCall()
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
}
}
CallViewModel.swift
import Foundation
class CallViewModel: ObservableObject {
#Published public var isIncomingCallActive = Bool()
init() {
self.isIncomingCallActive = false
}
func getCall() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.isIncomingCallActive = true
}
}
}
IncomingCallView.swift
import SwiftUI
struct IncomingCallView<RootView: View>: View {
private let rootView: RootView
#Binding var isActive: Bool
init(rootView: RootView, isActive: Binding<Bool>) {
self.rootView = rootView
self._isActive = isActive
}
var body: some View {
rootView
.background(Activator(isActive: $isActive))
}
struct Activator: UIViewRepresentable {
#Binding var isActive: Bool
#State private var myWindow: UIWindow? = nil
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
self.myWindow = view.window
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let holder = myWindow?.rootViewController?.view else { return }
if isActive && context.coordinator.controller == nil {
context.coordinator.controller = UIHostingController(rootView: loadingView)
let view = context.coordinator.controller!.view
view?.backgroundColor = UIColor.black.withAlphaComponent(1)
view?.isUserInteractionEnabled = true
view?.translatesAutoresizingMaskIntoConstraints = false
holder.addSubview(view!)
holder.isUserInteractionEnabled = true
view?.leadingAnchor.constraint(equalTo: holder.leadingAnchor).isActive = true
view?.trailingAnchor.constraint(equalTo: holder.trailingAnchor).isActive = true
view?.topAnchor.constraint(equalTo: holder.topAnchor).isActive = true
view?.bottomAnchor.constraint(equalTo: holder.bottomAnchor).isActive = true
} else if !isActive {
context.coordinator.controller?.view.removeFromSuperview()
context.coordinator.controller = nil
holder.isUserInteractionEnabled = true
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var controller: UIViewController? = nil
}
private var loadingView: some View {
HStack(spacing: 20) {
Button(action: {
print("acceptCall pressed")
// change UI for accepted call
}) {
Image("acceptCall")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
.aspectRatio(contentMode: .fit)
}
Button(action: {
// close the view
print("rejectCall pressed")
self.isActive = false
}) {
Image("rejectCall")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
.frame(width: 300, height: 300)
.background(Color.primary.opacity(0.7))
.cornerRadius(10)
}
}
}
Something in just swiftUI would look like this
struct AppView: View {
#StateObject var callVM = CallViewModel()
var body: some View {
ZStack {
TabView {
Text("TabOne")
.tabItem {
Image(systemName: "list.dash")
Text("Menu")
}
Text("TabTwo")
.tabItem {
Image(systemName: "square.and.pencil")
Text("Order")
}
}
if callVM.isIncomingCallActive {
ZStack {
Color.green
VStack {
Text("Incoming call...")
Button(action: { callVM.isIncomingCallActive = false }) {
Text("Cancel")
}
}
}.edgesIgnoringSafeArea(.all) // <= here
}
}
.onAppear {
self.callVM.getCall()
}
}
}
class CallViewModel: ObservableObject {
#Published public var isIncomingCallActive = false
func getCall() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.isIncomingCallActive = true
}
}
}