Can't restore state from code created ViewController - swift

I'm trying to restore ViewController created from code. I'm trying to make very simple example but I don't understand why it doesn't work. An application changes background color of view by clicking on image and should save it when app is stopped. I'm trying to store and restore Bool property, and basing on it app changes views background color. When I'm testing it I'm sending app to background by cmd + shift + h, after that stop app from xcode and open it again, but it doesn't save selected background color.
I've enabled restoration option in AppDelegate
In ViewController I've set restorationIdentifier and restorationClass
Adopted protocol UIViewControllerRestoration in extension to ViewController and implemented encoding and decoding methods
Implemented ViewController restoring method
AppDelegate class
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
}
ViewController class
class ViewController: UIViewController {
var sunny: Bool = true {
didSet {
setColor()
}
}
lazy var colorView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var changeColor: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Change", for: .normal)
button.addTarget(self, action: #selector(change), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.restorationIdentifier = "myVC"
self.restorationClass = ViewController.self
view.backgroundColor = .white
setupUI()
}
#objc func change() {
sunny.toggle()
}
func setColor() {
colorView.backgroundColor = sunny == true ? .yellow : .darkGray
}
}
extension ViewController: UIViewControllerRestoration {
func setupUI() {
view.addSubview(colorView)
NSLayoutConstraint.activate([
colorView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
colorView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
colorView.widthAnchor.constraint(equalToConstant: view.frame.width),
colorView.heightAnchor.constraint(equalToConstant: 500)
])
view.addSubview(changeColor)
NSLayoutConstraint.activate([
changeColor.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 10),
changeColor.centerXAnchor.constraint(equalTo: colorView.centerXAnchor),
changeColor.widthAnchor.constraint(equalToConstant: 100),
changeColor.heightAnchor.constraint(equalToConstant: 45)
])
}
override func encodeRestorableState(with coder: NSCoder) {
coder.encode(sunny, forKey: "isSunny")
super.encodeRestorableState(with: coder)
}
override func decodeRestorableState(with coder: NSCoder) {
guard let isSunny = coder.decodeBool(forKey: "isSunny") as? Bool else {return}
sunny = isSunny
setColor()
super.decodeRestorableState(with: coder)
}
static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
let vc = ViewController()
return vc
}
}

Related

RxSwift modelSelected Drive model on model View & Get that model on DetailView

This is my FirstView ( Parent VIew)
tableView.rx.modelSelected(Kinder.self)
.asDriver()
.drive(self.detailKinderViewModel.currentKinder)
.disposed(by: disposeBag)
This is ViewModel ( BehaviorRelay )
lazy var currentKinder = BehaviorRelay<Kinder>(value: Kinder())
This is My SecondView ( Child View )
override func viewDidLoad() {
super.viewDidLoad()
detailKinderViewModel.currentKinder
.asDriver(onErrorJustReturn:Kinder())
.map{$0.kinder_name}
.drive(self.navigationItem.rx.title)
.disposed(by: disposeBag)
}
I can get Only Default Model Data.
I want to Get current Data on Child View
There isn't enough code to figure out what your problem might be. Here is how I would do it using my Cause_Logic_Effect library.
import Cause_Logic_Effect
import RxCocoa
import RxSwift
import UIKit
extension FirstView {
func connect() {
Observable.just([
Kinder(name: "Ben"),
Kinder(name: "Mia"),
Kinder(name: "Leon"),
Kinder(name: "Emma")
])
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { _, kinder, cell in
if #available(iOS 14.0, *) {
var configuration = cell.defaultContentConfiguration()
configuration.text = kinder.name
cell.contentConfiguration = configuration
}
else {
cell.textLabel?.text = kinder.name
}
}
.disposed(by: disposeBag)
// when the user selects a cell, load its Kinder into a newly created
// SecondView and push it onto the navigation stack
tableView.rx.modelSelected(Kinder.self)
.bind(onNext: pushScene(on: navigationController!, animated: true) { kinder in
SecondView().scene { $0.connect(kinder: kinder) }
})
.disposed(by: disposeBag)
}
}
extension SecondView {
func connect(kinder: Kinder) -> Observable<Never> {
// use the Kinder
title = kinder.name
// this controller doesn't send any info back to its parent.
return .never()
}
}
// The view controllers.
final class FirstView: UIViewController {
var tableView: UITableView!
let disposeBag = DisposeBag()
override func loadView() {
super.loadView()
title = "Main"
tableView = UITableView(frame: view.bounds)
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(tableView)
}
}
final class SecondView: UIViewController {
override func loadView() {
super.loadView()
view.backgroundColor = .white
}
}
// The models
struct Kinder {
let name: String
}
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
// Create a FirstView, configure it with a connector, wrap it in a
// navigation controller and make that the root.
window?.rootViewController = UINavigationController(rootViewController: FirstView().configure { $0.connect() })
window?.makeKeyAndVisible()
return true
}
}

WKWebView won't display the webpage I need it to

Given the code below. I can't get the web page "https://baycare.clearstep.health/covid19" to show up. It shows up okay in Safari and I can get other pages to show up in the WKWebView. I have tried implementing all of the navigation and ui delegate methods to try and track down the problem but have failed to find anything.
The URL used to work, but the company has changed something and now it doesn't. Any help is appreciated.
The below is a complete program:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller = UIViewController()
.setup { viewController in
WKWebView(frame: viewController.view.bounds)
.setup {
viewController.view.addSubview($0)
$0.uiDelegate = WebViewUIDelegate.instance
$0.navigationDelegate = WebViewDelegate.instance
$0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
$0.load(URLRequest(url: URL(string: "https://baycare.clearstep.health/covid19")!))
}
}
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = controller
window?.makeKeyAndVisible()
return true
}
}
extension NSObjectProtocol {
#discardableResult
func setup(_ fn: (Self) -> Void) -> Self {
fn(self)
return self
}
}
final class WebViewUIDelegate: NSObject, WKUIDelegate {
static let instance = WebViewUIDelegate()
}
final class WebViewDelegate: NSObject, WKNavigationDelegate {
static let instance = WebViewDelegate()
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("This doesn't fire so no error?", error)
}
}
WKWebView is depricated version but still webview will works fine, I had same issue and I was doing same but then instead of WKWenView I used web view
import UIKit
import WebKit
class PolicyVC: UIViewController {
#IBOutlet weak var webView: UIWebView!
var isPolicyView = false
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: UIButton.ButtonType.custom)
button.setImage(UIImage(named: "ic_arrow_left"), for: .normal)
button.addTarget(self, action:#selector(popView), for: .touchUpInside)
button.frame=CGRect(x: 0, y: 0, width: 20, height: 20)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItems = [barButton]
setUpUI()
}
#objc func popView(){
self.navigationController?.popViewController(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.navigationBar.backgroundColor = .black
}
func setUpUI(){
if isPolicyView{
let url = URL(string: "https://www.termsfeed.com/privacy-policy/68c4cdaeba7e146a7d72bf57654520e7")
let urlRequest = URLRequest(url: url!)
webView.loadRequest(urlRequest)
}else{
let url = URL(string: "https://www.termsfeed.com/terms-conditions/0001db2c7a1061a55e2f933bb94102de")
let urlRequest = URLRequest(url: url!)
webView.loadRequest(urlRequest)
}
}
}

I cannot change the background color of collection view programmatically

I do not know why I cannot change the first screen programmatically. The first screen remains white even if I change the color of collection view background color. I need someone's help.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.makeKeyAndVisible()
let layout = UICollectionViewFlowLayout()
let navController = UINavigationController(rootViewController: PokemonController(collectionViewLayout: layout))
window?.rootViewController = navController
return true
}
This is the code inside the Controller file. I deleted the default controller file.
class PokemonController: UICollectionViewController {
//property
//Init
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponent()
}
func configureViewComponent() {
collectionView.backgroundColor = .white
navigationController?.navigationBar.barTintColor = .mainPink()
}
}
Thank you!
Did you try it like this
func configureViewComponent() {
backgroundColor = .white
navigationController?.navigationBar.barTintColor = .mainPink()
}
try this and let me know, if it works.
func configureViewComponent() {
collectionView.backgroundView?.backgroundColor = .red
navigationController?.navigationBar.barTintColor = .mainPink()
}

In keeping with the recent questions on closures used to pass data between VCs, how would I do the same when using a containerVC within the rootVC?

I saw a recent bountied question (can find the link if you wish to see it) about using closures to pass data between VCs where one VC was embedded in a navigation controller. While the use of a closure there was fairly easy since there was a direct point of contact between the two VCs (in the form a segue), I have been wondering how the same would work if this was not the case.
As an example, consider the following set up (similar to the OG question that inspired this post):
RootVC, which has a counter UILabel
A subContainer VC which takes up the lower half of RootVC, which has a button, pressing which should increment the UILabel on RootVC by one.
I have prepared the code as follows (with some code taken from the OG question):
RootVC:
class RootVC: UIViewController {
var tappedCount: Int = 0
let pagingContainer: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var label: UILabel = {
let label = UILabel()
label.text = "\(tappedCount)"
label.textAlignment = .center
label.font = UIFont(name: "Copperplate", size: 90)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(label)
view.addSubview(pagingContainer)
pagingContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
pagingContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
pagingContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
pagingContainer.heightAnchor.constraint(equalToConstant: 500).isActive = true
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
addChild(pageController)
pageController.didMove(toParent: self)
pageController.view.translatesAutoresizingMaskIntoConstraints = false
pagingContainer.addSubview(pageController.view)
pageController.view.heightAnchor.constraint(equalTo: pagingContainer.heightAnchor, multiplier: 1).isActive = true
pageController.view.widthAnchor.constraint(equalTo: pagingContainer.widthAnchor, multiplier: 1).isActive = true
pageController.view.topAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
pageController.view.bottomAnchor.constraint(equalTo: pagingContainer.bottomAnchor).isActive = true
pageController.view.leadingAnchor.constraint(equalTo: pagingContainer.leadingAnchor).isActive = true
pageController.view.trailingAnchor.constraint(equalTo: pagingContainer.trailingAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
}
}
SubContainerVC:
class SubContainerVC: UIViewController {
var callback : (() -> Void)?
let button: UIButton = {
let button = UIButton()
button.setTitle("Button!", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.backgroundColor = .green
return button
}()
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
//Pressing this button should increment the label on RootVC by one.
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
And the PageViewController swift file:
class PageViewController: UIPageViewController {
lazy var subViewControllers:[UIViewController] = {
return [SubContainerVC()]
}()
init(transitionStyle style:
UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [String : Any]? = nil) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
setViewControllerFromIndex(index: 0)
}
func setViewControllerFromIndex(index:Int) {
self.setViewControllers([subViewControllers[index]], direction: UIPageViewController.NavigationDirection.forward, animated: true, completion: nil)
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex <= 0 {
return nil
}
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex >= subViewControllers.count-1 {
return nil
}
return subViewControllers[currentIndex+1]
}
}
You can inject the closure downstream to SubContainerVC, this will result in the closure execution coming up upstream.
Something along the lines (kept only the relevant VC code):
class SubContainerVC {
var buttonCallback: () -> Void = { }
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
buttonCallback()
}
}
class PageViewController: UIViewController {
// Note that you don't need the extra closure call for lazy vars
lazy var subViewControllers = [SubContainerVC()] {
didSet {
// just in case the controllers might change later on
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
var buttonCallback: () -> Void = { } {
didSet {
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
}
class RootVC: UIViewController {
var tappedCount: Int = 0 {
didSet {
label.text = "\(tappedCount)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
// this will trigger the `didSet` from PageViewController, resulting
// in the callback being propagated downstream
pageController.buttonCallback = { self.tappedCount += 1 }
}
}

viewWillAppear/DidAppear called twice when changing rootViewController with an Animation

Here is the code of my animation/transition :
if let window = self.window where animated {
window.rootViewController?.dismissViewControllerAnimated(true, completion: {
UIView.transitionFromView(window.rootViewController!.view, toView: tabBarController.view, duration: 0.8, options: .TransitionFlipFromLeft, completion: { _ in
window.rootViewController = tabBarController
})
})
}
Without the animation code, (just setting the rootViewControlller), the problem does not appear.
Any idea why it makes the view appear twice and how I could fix this ?
using UIViewController.transitionFromViewController1 ,instead of your UIView.transitionFromView.act like it :
//
// AppDelegate.swift
// helloapp
//
// Created by quota on 2/18/16.
// Copyright © 2016 liu. All rights reserved.
//
import UIKit
class ViewController1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.greenColor()
let button = UIButton()
button.setTitle("transit", forState: .Normal)
button.frame = CGRectMake(20, 20, 100,20)
button.addTarget(self, action: "click:", forControlEvents: .TouchDown)
view.addSubview(button)
}
func click(sender:UIButton!){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let window = appDelegate.window {
// let v2 = ViewController2()
if let r = window.rootViewController{
r.transitionFromViewController(
appDelegate.v1!
,toViewController:appDelegate.v2!,duration:0.8,options: .TransitionFlipFromLeft,animations:nil){_ in }
}
}
}
}
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blueColor()
}
override func viewWillAppear(animated: Bool) {
print(animated)
}
}
class ViewController3: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var v1 : ViewController1?
var v2 : ViewController2?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = ViewController3()
v1 = ViewController1()
v2 = ViewController2()
v1!.view.frame = self.window!.frame
self.window!.rootViewController?.addChildViewController(v1!)
self.window!.rootViewController?.view.addSubview(v1!.view)
self.window!.rootViewController?.addChildViewController(v2!)
self.window?.makeKeyAndVisible()
return true
}
}