DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation
  • SwiftData Dependency Injection in SwiftUI Application
  • HLS Streaming With AVKit and Swift UI for iOS and tvOS
  • Key Features of Swift Programming Language

Trending

  • Driving DevOps With Smart, Scalable Testing
  • Agentic AI and Generative AI: Revolutionizing Decision Making and Automation
  • Next-Gen IoT Performance Depends on Advanced Power Management ICs
  • Medallion Architecture: Why You Need It and How To Implement It With ClickHouse
  1. DZone
  2. Coding
  3. Frameworks
  4. Updating SwiftUI Views From Objective-C Using MVVM

Updating SwiftUI Views From Objective-C Using MVVM

This article takes you through experimenting with a non-standard situation when you need to update SwiftUI component from Objective-C implementation.

By 
Max Kalik user avatar
Max Kalik
·
Mar. 30, 23 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
3.2K Views

Join the DZone community and get the full member experience.

Join For Free

On the internet, in the Apple documentation, and on Medium, there is a ton of information about SwiftUI, how to use it from UIKit, and vice versa. But today, let’s consider a not-so-common case in real life that’s useful to reflect on non-standard situations in iOS development.

Task: Show SwiftUI view from Objective-C codebase. Moreover, when an Objective-C model should be observable, the respective SwiftUI should be updated.

Result: When you press the Sign In button from Home, View Controller should be pushed a SwiftUI View with a Title, Text Field, and a button Sign In. On editing TextView and on pressing the Sign In button, the title label should be updated.

SwiftUIStart-Up MVVM+C Project

Clone the project from the link above. The repository has the start folder, which has an initial project that we are going to use.

This iOS App was developed using MVVM + Coordinator. I agree it’s already a bit complicated to use this architecture to explain how to show SwitUI views and update them from Objective-C, but I would say it’s more like a real situation where we need to stick to a particular pattern. No worries, we have only one screen at this point.

Sign In Screen

The structure of the project consists of several entities: AppCoordinator, Protocols, and MVVM Modules. AppCoordinator has only a general App Coordinator. For the protocols, I decided to store there all protocols we will use across the project. And the Modules group has the screens; for now, it’s only one home screen with a black Sign In button.

AppCoordinator

The Sign In button uses a typical coordinator delegate where from the coordinator, we have to prepare our solution of showing SwiftUI Login View. But before implementing SwiftUI View, let’s complicate our task a little bit more. Let’s assume we already have LoginViewModel implemented it in Objective-C as well, and we are not allowed to refactor it into Swift. Only LoginView should be implemented in SwiftUI.

LoginViewModel

Okay. Let’s take a look at LoginViewModel. We have three NSString properties.

Objective C
 
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LoginViewModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *buttonTitle;

- (void) buttonTapped;

@end

NS_ASSUME_NONNULL_END


The name property should store from SwiftUI TextView. The title should be changed on TextView edit. The buttonTitle will be “Sign In,” and the method buttonTap should change the title value. Nothing crazy.

Objective C
 
#import "LoginViewModel.h"

@implementation LoginViewModel

- (instancetype) init {
    self = [super init];
    if (!self) return nil;
    
    [self prepareViewModel];
    
    return self;
}

- (void) prepareViewModel {
    self.title = @"Hello!";
    self.buttonTitle = @"Sign In";
}

- (void)buttonTapped {
    // update title with value "Signed In"
}

@end


Bridging Swift Files

It’s time to start adding Swift files to our Objective-C project.

Go to File ➝ New ➝ File… ➝ SwiftUI View. This will keep the project structure better when saving this file to the Modules/Login/Views/ folder.

After this, you will be asked about creating Bridging Header, and yes, we need this. Press create.

Create Bridging Header

You will see two files: LoginViewModel.swift and UIKitObjCToSwiftUI-Bridging-Header.h in the Views group.

Views

Initially bridging header after creation will be empty. But we already know that we are going to use LoginViewModel.h. So we need to import this header.

Last Step

The last step of bridging is to make our Objective-C files see Swift files. So, we need another import for that, and we will do this in AppCoordinator.m. But a bit later.

SwiftUI View and Coordinator Interface

At this moment, we don’t use any view models. We just need to show a pure SwiftUI view with dummy data.

Swift
 
struct LoginView: View {
    
    var body: some View {
        VStack {
            Text("Title")
            TextField("Enter your name", text: .constant("name"))
                .multilineTextAlignment(.center)
                .padding(.horizontal, 40)
            Button {
                // button tap action
            } label: {
                Text("Sign In")
                    .font(.system(size: 17, weight: .bold))
                    .foregroundColor(Color.white)
                    .padding()
            }
            .background(Color.green)
            .cornerRadius(10)
            .padding(.top, 20)
        }
    }
}


To show the SwiftUI view, we need to use UIHostingController which will play the role of UIViewController in Objective-C world, so for that, we need to create an “interface” to wire up AppCoordinator and SwiftUI views.

Swift
 
@objc
class SwiftUICoordinatorInterface: NSObject {

    @objc
    func loginView() -> UIViewController {
        let view = LoginView()
        return UIHostingController(rootView: view)
    }
}


It’s time to bridge to make Objective-C see Swift. Let’s import our Bridge header right in the AppCoordinator.m.

import bridge header

By the way, don’t be scared. You will notice that autocomplete is not working for this header. The last part of this file should be -Swift.h. If you are not sure about that, check:

Build Settings ➝ Swift Compiler ➝ General ➝ Objective-C Generated Interface Header Name

Swift Compiler

Let’s use all these pieces in the AppCoordinator so the (void)showLogin method will be updated:

Objective C
 
- (void)showLogin {
    UIViewController *viewController = [[[SwiftUICoordinatorInterface alloc] init] loginView];
    [self.navigationController pushViewController: viewController animated: YES];
}


Run build. If you have done it all correctly, you will see a SwiftUI view after tapping on the Sign In button from the Home View Controller.

Objective-C ViewModel and ViewModel Interface

We need to make our view model visible in the SwiftUI view, and the title of the Login View should be updated. An Implementation will be a little bit similar to Coordinator interface but with some additional enhancements.

To unify our “observable” solution, we need a couple of protocols: ObservableNSObject and ObservableNSObjectDelegate. Here’s the code:

Objective C
 
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

#pragma mark - ObservableNSObjectDelegate

@protocol ObservableNSObject;
@protocol ObservableNSObjectDelegate <NSObject>

@required
- (void)viewModelDidUpdate:(id<ObservableNSObject>)viewModel;

@end

#pragma mark - ObservableNSObject

@protocol ObservableNSObject <NSObject>

@required
@property (nonatomic, weak) id <ObservableNSObjectDelegate> observedViewModelDelegate;

@end

NS_ASSUME_NONNULL_END


The trick is to use these protocols for conformance of all Objective-C modules, which will be used in SwiftUI and the viewModelDidUpdate method. They will do this for each update of properties or methods in the view model.

Objective C
 
@interface LoginViewModel : NSObject <ObservableNSObject>


In LoginViewModel.m, update these methods:

Objective C
 
- (void)prepareTitleWithName:(NSString *)name {
    if ([name length] != 0) {
        self.title = [NSString stringWithFormat:@"Hello, %@!", name];
    } else {
        self.title = @"Hello!";
    }
}

-(void)setName:(NSString *)name {
    if (_name != name) {
        _name = name;
        [self prepareTitleWithName:name];
        [self.observedViewModelDelegate viewModelDidUpdate:self];
    }
}

- (void)buttonTapped {
    [self setTitle:@"Signed in"];

    // Update view model
    [self.observedViewModelDelegate viewModelDidUpdate:self];
}


Since we prepared the view model to be updated, let’s create our view model interface, as shown below:

Swift
 
class SwiftUIViewModelInterface<ViewModel: ObservableNSObject>: NSObject, ObservableObject, ObservableNSObjectDelegate {
    
    @Published var viewModel: ViewModel

    init(viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init()
        viewModel.observedViewModelDelegate = self
    }
    
    func viewModelDidUpdate(_ viewModel: ObservableNSObject) {
        guard let viewModel = viewModel as? ViewModel else { return }
        self.viewModel = viewModel
    }
}


The main trick is to update the published viewModel using ObservableNSObjectDelegate. Let’s update our LoginView using this view model interface. I called it observable.

Swift
 
struct LoginView: View {
    
    typealias ViewModel = SwiftUIViewModelInterface<LoginViewModel>
    @ObservedObject var observed: ViewModel

    init(viewModelInterface: ViewModel) {
        self.observed = viewModelInterface
    }
    
    var body: some View {
        VStack {
            Text(observed.viewModel.title)
            TextField("Enter your name", text: $observed.viewModel.name)
                .multilineTextAlignment(.center)
                .padding(.horizontal, 40)
            Button {
                self.observed.viewModel.buttonTapped()
            } label: {
                Text(self.observed.viewModel.buttonTitle)
                    .font(.system(size: 17, weight: .bold))
                    .foregroundColor(Color.white)
                    .padding()
            }
            .background(Color.green)
            .cornerRadius(10)
            .padding(.top, 20)
        }
    }
}


Certainly, we need to update Coordinator interface. As you can see, the SwiftUIViewModelInterface is like middleware between the Objective-C view model and SiwftUI View.

Swift
 
@objc func loginView(viewModel: LoginViewModel) -> UIViewController {
  let viewModelInterface = SwiftUIViewModelInterface(viewModel: viewModel)
  let view = LoginView(viewModelInterface: viewModelInterface)
  return UIHostingController(rootView: view)
}


The AppCoordinator showLogin also should be updated with the LoginViewModel. Of course, ideally, it will be cool to see the SwiftUI object, but Objective-C sees only the UIKit stuff.

Objective C
 
- (void)showLogin {
    LoginViewModel *viewModel = [[LoginViewModel alloc] init];
    UIViewController *viewController = [[[SwiftUICoordinatorInterface alloc] init] loginViewWithViewModel:viewModel];
    [self.navigationController pushViewController: viewController animated: YES];
}


That’s it. Run build, and you can see how it works. You can find the result in final folder of the project repository: https://github.com/maxkalik/uikit-objc-to-swiftui

Wrapping Up

In my opinion, the best part of this task is the non-standard situation, and I’m sure you most likely won’t be encountered this. But the question of using SwiftUI in Objective-C exists, and why wouldn’t you try to answer it?

I call it — an uncomfortable task. It means this kind of task confuses you, and you don’t know exactly how to make this at the beginning. They force you to think out of the box, which will make you more professional.

Objective C Swift (programming language) UI Use case

Published at DZone with permission of Max Kalik. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation
  • SwiftData Dependency Injection in SwiftUI Application
  • HLS Streaming With AVKit and Swift UI for iOS and tvOS
  • Key Features of Swift Programming Language

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!