Embed iOS UIViewController in a Flutter widget - ios

I have a Flutter fullscreen modal widget with a header, a footer and some content which should be rendered natively for iOS. I know I can host iOS UIViews in Flutter using Platform Views and I managed to do all the logic to get this working.
My issue is that I need to host a whole view controller within this widget, not only a simple view, and this view controller belongs to a third-party framework.
An option would be implementing the header and footer natively, but this would take a lot of time since this would involve passing lot of data, performing network requests, adding callbacks and so on. I read online that a UIKitViewController exists, but it can only be created from PlatformViewServices, which is still a work in progress and should not be used. I didn't manage to find proper documentation online.

I think you can try this.
class NativeView: NSObject, FlutterPlatformView {
private var _vc: UIViewController
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger?
) {
_vc = UIViewController()
super.init()
}
func view() -> UIView {
return _vc.view
}
}
Calling _vc.view will call loadView() and viewDidLoad() when view is not initialized yet.

Related

buildMenu is called in AppDelegate but not UIViewController

I'm attempting to create a custom menu for each view in my app, however it appears buildMenu is not being called in View Controllers. Here's an example:
In my AppDelegate, this code is used, which works 100% as expected.
override func buildMenu(with builder: UIMenuBuilder) {
print("Updating menu from AppDelegate")
super.buildMenu(with: builder)
let command = UIKeyCommand(
input: "W",
modifierFlags: [.command],
action: #selector(self.helloWorld(_:))
)
command.title = "Hello"
builder.insertChild(UIMenu(
__title: "World",
image: nil,
identifier: UIMenu.Identifier(rawValue: "com.hw.hello"),
options: [],
children: [command]
), atEndOfMenu: .file)
}
#objc private func helloWorld(_ sender: AppDelegate) {
print("Hello world")
}
However I need to change the options available in the menu depending on where the user is in the app, so I tried doing this in a UIViewController:
override func viewDidAppear(_ animated:Bool){
// Tried all of these to see if any work
UIMenuSystem.main.setNeedsRebuild()
UIMenuSystem.context.setNeedsRebuild()
UIMenuSystem.main.setNeedsRevalidate()
UIMenuSystem.context.setNeedsRevalidate()
}
and again..
// This is never called
override func buildMenu(with builder: UIMenuBuilder) {
print("Updating menu in View Controller")
}
but the buildMenu in the UIViewController is never called :(
Any ideas if this is intended behavior or if there are any workarounds?
For main menus, the system only consults UIApplication and UIApplicationDelegate, since main menus can exist without any window and hence without any UIViewController hierarchy. That's why your override on UIViewController doesn't get called for main menus.
For context menus, the system does consult the full responder chain starting at the view.
If you need to update main menu commands depending on their context:
You could leave buildMenu(with:) in UIApplicationDelegate, arrange for delegate to figure out when and what changed and call UIMenuSystem.main.setNeedsRebuild() when it does change, or
You could define a private method buildMyMenu(with:) in your UIViewController subclasses, and arrange for buildMenu(with:) in UIApplicationDelegate to call it, or
You could build a static menu in buildMenu, and rely on your overrides of canPerformAction(_:withSender:) and validate(_:) to enable or disable or even hide particular commands e.g. by updating the attributes property in your validate(_:) override.
This is the intended behavior. Quoting from docs:
Because menus can exist with no window or view hierarchy, the system only consults UIApplication and UIApplicationDelegate to build the app’s menu bar.
The same docs page explains how you can adjust the menu commands from view controllers, and there is a great sample project too, so make sure to check it.

iOS Protocol function call on viewDidLoad/setup

I'm trying to create a protocol that I can use on UIViewControllers that will do some setup work when the protocol is attached to a UIViewControler. I currently have the following code.
protocol MyProtocol {
}
extension MyProtocol where Self: UIViewController {
func setup() {
print("We have successfully setup this view controller")
}
}
class MyViewController: UIViewController, MyProtocol {
override func viewDidLoad() {
super.viewDidLoad()
print("Other setup work here") // This line might or might not exist
setup() // Goal is to remove this line, so I don't forget to add it across all my view controllers
}
}
My question is, is there a way to remove the setup() call from within the viewDidLoad function? I think it'd be a lot safer to not have to call that function every time. If there is a view controller that forgets to add that one call, then the setup won't happen, and I want to try to prevent that.
There is a chance that the viewDidLoad function on the view controller that it is attached to will do other work (ex. in this example print("Other setup work here")), or there is a chance it won't do anything except for that setup call.
I'm also not completely opposed to moving that setup function call into a separate function within the view life cycle, but again those other functions in the view life cycle that get called might have other things that need to run as well, so I don't want to completely override them.
I have also considered using the init method somehow, but I think the problem with that is that the view won't have been loaded yet and therefor I can't do the proper setup work like changing a label's text and such.

Ensure that property observer didSet manipulates User Interface after viewDidLoad

I am working on an open source tutorial using MVVM, Coordinators and RxSwift. I am constructing all the viewcontrollers and models in the coordinator. Controller has a strong reference to viewmodel and when a viewmodel is set, I would like to perform some UI related actions(using property observer didSet). The problem I am facing is that didSet is called before viewDidLoad causing a crash.
Stripped down version of ViewController:
class MessageVC: UIViewController {
var viewModel: MessageViewModel! {
didSet {
manipulateUI() // crashes
}
}
override func viewDidLoad() {
super.viewDidLoad()
manipulateUI() // works fine if setup is correct in coordinator
}
Coordinator stripped down version:
extension AppCoordinator {
convenience init() {
let rootVC = MessageVC() // actual construction from storyboard
let messages = Message.getMessages()
rootVC.viewModel = MessageViewModel(withMessage: messages)
}
My concern is that even though calling manipulateUI in viewDidLoad is working for me currently, the app will crash if I forget to set the viewModel from my co-ordinator making me think that I am using a fragile architecture. I really like updating userinterface from didSet but it is called before viewDidLoad.
I know it is a simple problem but from architecture standpoint it seems fragile. Any suggestions, improvements and comments are appreciated a lot.
I wont say that cases like this can define wether you are dealing with fragile architecture or not because view controllers has their own life cycle which differs a lot from other objects life cycle. Anyway you can easily avoid crashes here using different approaches. For example :
Approach 1:
Put a guard statement at the very beginning of your manipulateUI function so this function wont manipulate UI until both view is loaded and model is set. Then call this function on viewDidLoad method and when viewModel is set:
func manipulateUI(){
guard let viewModel = self.viewModel , isViewLoaded else {
return
}
//continue manipulation here
}
Approach 2:
Since you are not sure wether view is loaded when you set the model and don't know if views are initialized yet, you can access the views as optional properties in manipulateUI function:
func manipulateUI(){
self.someLabel?.text = self.viewModel.someText
//continue manipulation here
}
Approach 3:
Since you are using RxSwift you can always register an observer for view controller's isViewLoaded property and set the data source after you are sure that view is loaded
Crash happens because at this point
rootVC.viewModel = MessageViewModel(withMessage: messages)
view controller is not initialized.
It won't work the way you're trying to accomplish, you have to call manipulateUI() inside viewDidLoad.

Where to put logic shared by multiple view controllers

I'm working on an iPhone app (Objective-C) which has barcode scanning functionality on many of its screens. The user can tap a control to recognize different barcodes and navigate to different screens depending on what type of barcode is recognized. The majority of the logic does not depend on which screen they initiated the scan from... As such, I don't want to duplicate the code in each view controller, but am uncertain where the best place for it is. It requires the user tapping on a detection rectangle, so it does need to be able to handle these events. Many thanks!
You could use a singleton
class DataStore {
static let sharedDataStore = DataStore()
func scanBarcode() {
//logic
}
}
calling the function in another View Controller
class viewController: UIViewController {
let shared = DataStore.sharedDataStore
override func viewDidLoad() {
//calling the function
shared.scanBarcode()
}

How to subscribe to delegate events globally?

I have a custom delegate that triggers certain events. For context, it's a bluetooth device that fires events arbitrarily. I'd like my view controllers to optionally subscribe to these events that get triggered by the device delegate.
It doesn't make sense that each view controller conforms to the custom delegate because that means the device variable would be local and would only fire in that view controller. Other view controllers wouldn't be aware of the change. Another concrete example would be CLLocationManagerDelegate - for example what if I wanted all view controllers to listen to the GPS coordinate changes?
Instead, I was thinking more of a global delegate that all view controllers can subscribe to. So if one view controller triggers a request, the device would call the delegate function for all subscribed view controllers that are listening.
How can I achieve this architectural design? Are delegates not the right approach? I thought maybe NotificationCenter can help here, but seems too loosely typed, perhaps throwing protocols would help makes things more manageable/elegant? Any help would be greatly appreciated!
You could have an array of subscribers that would get notified.
class CustomNotifier {
private var targets : [AnyObject] = [AnyObject]()
private var actions : [Selector] = [Selector]()
func addGlobalEventTarget(target: AnyObject, action: Selector) {
targets.append(target)
actions.append(action)
}
private func notifyEveryone () {
for index in 0 ..< targets.count {
if targets[index].respondsToSelector(actions[index]) {
targets[index].performSelector(actions[index])
}
}
}
}
Naturally, you'd have to plan further to maintain the lifecycle of targets and actions, and provide a way to unsubscribe etc.
Note: Also ideal would be for the array of targets and actions to be an of weak objects. This SO question, for instance, deals with the subject.
• NotificationCenter is first solution that comes in mind. Yes, it is loosely typed. But you can improve it. For example like this:
extension NSNotificationCenter {
private let myCustomNotification = "MyCustomNotification"
func postMyCustomNotification() {
postNotification(myCustomNotification)
}
func addMyCustomNotificationObserverUsingBlock(block: () -> ()) -> NSObjectProtocol {
return addObserverForName(myCustomNotification, object: nil, queue: nil) { _ in
block()
}
}
}
• Second solution would be to create some shared object, which will store all delegates or blocks/closures and will trigger them when needed. Such object basically will be the same as using NotificationCenter, but gives you more control.

Resources