Connecting UIKit with SwiftUI, why did I lose my NavBar? - swift

So when I use the old UIKit Lifecycle with SceneDelegate and AppDelegate I get the desired result. But when I use the new App life cycle I can't figure out to get the NavBar back..
import SwiftUI
struct ContentView: View {
var body: some View {
DiffableContainer()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
DiffableContainer()
.ignoresSafeArea()
}
}
struct DiffableContainer: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return UINavigationController(rootViewController: DiffableTableVC())
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
class DiffableTableVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = .green
navigationItem.title = "Test"
title = "Hello"
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("This is set up and running")
return true
}
}
Here is the lifecycle that I am using now:
#main
struct ContactsApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Related

Set property on ViewController in UIViewControllerRepresentable

How can I change a property on a UIViewController presented via. a UIViewControllerRepresentable ?
Sample code of how I would expect it to work, however it doesn't. How can I make it work?
(color is just a example, please don't focus on that)
class MyViewController: UIViewController {
var color: UIColor? = nil {
didSet {
guard isViewLoaded else { return }
view.layer.backgroundColor = color?.cgColor
}
}
override func viewDidLoad() {
view.layer.backgroundColor = color?.cgColor
}
}
struct MyView: UIViewControllerRepresentable {
#State private var color: UIColor?
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
let viewController = MyViewController()
viewController.color = color // always nil?
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = color // always nil?
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.color = color // does nothing?
return self
}
}
struct ContentView: View {
var body: some View {
MyView()
.color(.magenta)
}
}
Here is possible approach (if you expect that color can be modified externally, as it is seen). Tested with Xcode 11.4 / iOS 13.4
struct MyView: UIViewControllerRepresentable {
#Binding var color: UIColor?
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
let viewController = MyViewController()
viewController.color = color // always nil?
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = color // always nil?
}
}
struct ContentView: View {
#State private var color: UIColor? = .magenta
var body: some View {
MyView(color: $color)
// MyView(color: .constant(.magenta)) // alternate usage
}
}
Another solution based on idea from Asperi's answer:
struct MyView: UIViewControllerRepresentable {
private class State: ObservableObject {
var color: UIColor?
}
#Binding private var state: State
init() {
_state = .constant(State())
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
return MyViewController()
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
uiViewController.color = state.color
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.state.color = color
return self
}
}
Or a even simpler version, we just use #Binding for the wrapped ViewController directly
struct MyView: UIViewControllerRepresentable {
#Binding private var viewController: MyViewController
init() {
_viewController = .constant(MyViewController())
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
return viewController
}
func updateUIViewController(_ uiViewController: MyViewController,
context: UIViewControllerRepresentableContext<MyView>) {
}
}
extension MyView {
func color(_ color: UIColor) -> MyView {
self.viewController.color = color
return self
}
}

How to change position of SearchBar in NavigationBar?

I followed this article on how to display a SearchBar in the NavigationBar. I integrated it like this into my view:
struct ExploreView: View {
#ObservedObject var searchBar = SearchBar()
var body: some View {
NavigationView {
ZStack {
Color(red: 250/255, green: 250/255, blue: 250/255)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
Image(R.image.navigationBarBackground)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width)
.edgesIgnoringSafeArea(.all)
Spacer()
}
}
.navigationBarTitle("", displayMode: .inline)
.add(self.searchBar)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
class SearchBar: NSObject, ObservableObject {
#Published var text: String = ""
let searchController: UISearchController = UISearchController(searchResultsController: nil)
override init() {
super.init()
self.searchController.obscuresBackgroundDuringPresentation = false
self.searchController.searchResultsUpdater = self
}
}
extension SearchBar: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
// Publish search bar text changes.
if let searchBarText = searchController.searchBar.text {
self.text = searchBarText
}
}
}
struct SearchBarModifier: ViewModifier {
let searchBar: SearchBar
func body(content: Content) -> some View {
content
.overlay(
ViewControllerResolver { viewController in
viewController.navigationItem.searchController = self.searchBar.searchController
}
.frame(width: 0, height: 0)
)
}
}
extension View {
func add(_ searchBar: SearchBar) -> some View {
return self.modifier(SearchBarModifier(searchBar: searchBar))
}
}
final class ViewControllerResolver: UIViewControllerRepresentable {
let onResolve: (UIViewController) -> Void
init(onResolve: #escaping (UIViewController) -> Void) {
self.onResolve = onResolve
}
func makeUIViewController(context: Context) -> ParentResolverViewController {
ParentResolverViewController(onResolve: onResolve)
}
func updateUIViewController(_ uiViewController: ParentResolverViewController, context: Context) {
}
}
class ParentResolverViewController: UIViewController {
let onResolve: (UIViewController) -> Void
init(onResolve: #escaping (UIViewController) -> Void) {
self.onResolve = onResolve
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("Use init(onResolve:) to instantiate ParentResolverViewController.")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if let parent = parent {
onResolve(parent)
}
}
}
It look's like this: SearchBar: inactive, SearchBar: active
But I would like to have the inactive SearchBar in the same position as the active SearchBar to avoid the free space. In the end it should look like the SearchBar in the Instagram App. Does anyone know how to do this?
Add this piece of code inside your init() method of SearchBar. It will make search bar at same position when it's active.
self.searchController.hidesNavigationBarDuringPresentation = false
If you want to set search bar to navigation bar title instead of text, inside your overlay(_:)
Change this code
ViewControllerResolver { viewController in
viewController.navigationItem.searchController = self.searchBar.searchController
}
To
ViewControllerResolver { viewController in
viewController.navigationItem.titleView = self.searchBar.searchController.searchBar
}

Is possible to combine together in same View SwiftUIView and UIViewControllerRepresentable?

I am wondering if it's possible to combine a View and a UIViewControllerRepresentable in a same view.
I tried:
//Here I declare MyViewController:
class MyViewController: UIViewController {
override viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
}
struct MyViewControllerIntegrate: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<MyViewControllerIntegrate>) -> MyViewController {
return MyViewController()
}
func updateUIViewController(_ uiViewController: MyViewController, context: UIViewControllerRepresentableContext<MyViewControllerIntegrate>) {
}
}
struct MyView: View {
var body: some View {
Text("Hello StackOverflow!")
}
}
struct ContentView: View {
var body: some View {
MyView()
MyViewController()
.frame(height: 400)
}
}
Xcode shows me an error message:
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Any hints? Thank you
Put them in Group (or in some stack, eg. VStack)
struct ContentView: View {
var body: some View {
Group {
MyView()
MyViewController()
.frame(height: 400)
}
}
}

Does somebody have all ready implemented searchbar on tvos with swiftui?

Does somebody have all ready implemented a search bar using Apple component like UISearchBar with swiftui on tvos ?
I tried this UISearchBar(frame: .zero) but I got this error init(frame:)' is unavailable in tvOS
I only found solutions for ios.
The scheme of initial setup should be like below. Of course, the logic of searching/filtering/showing results is app specific.
import SwiftUI
import TVUIKit
struct SearchView: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<SearchView>) -> UINavigationController {
let controller = UISearchController(searchResultsController: context.coordinator)
controller.searchResultsUpdater = context.coordinator
return UINavigationController(rootViewController: UISearchContainerViewController(searchController: controller))
}
func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<SearchView>) {
}
func makeCoordinator() -> SearchView.Coordinator {
Coordinator()
}
typealias UIViewControllerType = UINavigationController
class Coordinator: UIViewController, UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
// do here what's needed
}
}
}
struct ContentView: View {
#State private var text: String = ""
var body: some View {
VStack {
SearchView()
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI coordinator not updating the containing view's property

So I've wrapped WKWebView in an UIViewRepresentable and built a coordinator in order to access its navigation delegate's functions. In the webView(_:didFinish:) function I am trying to update the view's didFinishLoading variable. If I print right after assigning, it prints true - the expected behavior. But, in the parent view, when I call the getHTML function, it prints false - even if I wait until the WKWebView is fully loaded.
Here is the code:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
#Binding var link: String
init(link: Binding<String>) {
self._link = link
}
private var didFinishLoading: Bool = false
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
self.webView.load(URLRequest(url: URL(string: self.link)!))
self.webView.navigationDelegate = context.coordinator
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var webView: WebView
init(_ webView: WebView) {
self.webView = webView
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("WebView: navigation finished")
self.webView.didFinishLoading = true
}
}
func makeCoordinator() -> WebView.Coordinator {
Coordinator(self)
}
func getHTML(completionHandler: #escaping (Any?) -> ()) {
print(self.didFinishLoading)
if (self.didFinishLoading) {
self.webView.evaluateJavaScript(
"""
document.documentElement.outerHTML.toString()
"""
) { html, error in
if error != nil {
print("WebView error: \(error!)")
completionHandler(nil)
} else {
completionHandler(html)
}
}
}
}
}
struct WebView_Previews: PreviewProvider {
#State static var link = "https://apple.com"
static var previews: some View {
WebView(link: $link)
}
}
Here is your code, a bit modified for demo, with used view model instance of ObservableObject holding your loading state.
import SwiftUI
import WebKit
import Combine
class WebViewModel: ObservableObject {
#Published var link: String
#Published var didFinishLoading: Bool = false
init (link: String) {
self.link = link
}
}
struct WebView: UIViewRepresentable {
#ObservedObject var viewModel: WebViewModel
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
self.webView.navigationDelegate = context.coordinator
if let url = URL(string: viewModel.link) {
self.webView.load(URLRequest(url: url))
}
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(_ viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("WebView: navigation finished")
self.viewModel.didFinishLoading = true
}
}
func makeCoordinator() -> WebView.Coordinator {
Coordinator(viewModel)
}
}
struct WebViewContentView: View {
#ObservedObject var model = WebViewModel(link: "https://apple.com")
var body: some View {
VStack {
TextField("", text: $model.link)
WebView(viewModel: model)
if model.didFinishLoading {
Text("Finished loading")
.foregroundColor(Color.red)
}
}
}
}
struct WebView_Previews: PreviewProvider {
static var previews: some View {
WebViewContentView()
}
}