Writing a New View¶
Introduction¶
This guide will use the Button
class implementation as an example to demonstrate the steps needed to extend the Boden Framework with new view components.
In contrast to Writing a View Module, this guide will explain how to extend the Boden Framework itself with a new integrated view component rather than adding an optional component which can be shipped independently. Please refer to Writing a View Module if you want to create a standalone view component.
The View¶
Boden views consist of two parts:
- The outward facing platform-independent
View
class. This is what users of your view component will usually see and interact with. - The internal platform-specific
View::Core
implementation. This part of the view implementation is usually hidden from users of your new view component.
The first step in writing a new view is to create a new class derived from View
.
Header¶
Create a new header in framework/ui/include/bdn
called ExampleButtonView.h
:
#pragma once
#include <bdn/ui/View.h>
namespace bdn
{
class ExampleButtonView : public ui::View
{
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
};
}
Source¶
In boden/framework/ui/src
, create a file called ExampleButtonView.cpp
:
#include <bdn/ExampleButtonView.h>
namespace bdn
{
ExampleButtonView::ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory)
: View(viewCoreFactory)
{
}
}
The View Core¶
Create a new Core
interface as an inner class of ExampleButtonView
:
#pragma once
namespace bdn
{
class ExampleButtonView : public ui::View
{
// ...
public:
class Core
{
};
};
}
Full
#pragma once
namespace bdn
{
class ExampleButtonView : public ui::View
{
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
class Core
{
};
};
}
The iOS Implementation¶
Header¶
The core's actual implementation is platform-specific. Create a new Objective-C++ header in framework/ui/platforms/ios/include/bdn/ios/
called ExampleButtonViewCore.hh
:
#pragma once
#include <bdn/ExampleButtonView.h>
#include <bdn/ExampleButtonViewCore.h>
#import <bdn/ios/ViewCore.hh>
namespace bdn::ios
{
class ExampleButtonViewCore : public ViewCore,
virtual public bdn::ExampleButtonView::Core
{
public:
ExampleButtonViewCore(
const std::shared_ptr<bdn::ViewCoreFactory> &viewCoreFactory);
};
}
Source¶
Create a new Objective-C++ source file in framework/ui/platforms/ios/src
called ExampleButtonViewCore.mm
:
#import <bdn/ios/ExampleButtonViewCore.hh>
@interface ExampleUIButton : UIButton <UIViewWithFrameNotification>
@property(nonatomic, assign) std::weak_ptr<bdn::ios::ExampleButtonViewCore> core;
@end
@implementation ExampleUIButton
- (void)setViewCore:(std::weak_ptr<bdn::ios::ViewCore>)viewCore
{
self.core = std::dynamic_pointer_cast<bdn::ios::ExampleButtonViewCore>(viewCore.lock());
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if(auto core = self.core.lock()) {
core->frameChanged();
}
}
namespace bdn::ios
{
ExampleButtonViewCore::ExampleButtonViewCore(const std::shared_ptr<ViewCoreFactory>& viewCoreFactory)
: ViewCore(viewCoreFactory, [ExampleUIButton buttonWithType:UIButtonTypeSystem])
{
}
}
Note
For more information about the intricacies of implementing view cores please refer to ViewCore and iOS ViewCore.
Connecting the View Core to the View¶
Boden uses the ViewCoreFactory
to create instances of the view core. To create the connection between a view and its core, add the following boilerplate to the classes:
View Header¶
Add the following code to ExampleButtonView.h
:
#include <bdn/ViewUtilities.h>
namespace bdn::detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
ExampleButtonView.h
#pragma once
#include <bdn/ui/View.h>
#include <bdn/ViewUtilities.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
class ExampleButtonView : public ui::View
{
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
class Core
{
};
};
}
View Implementation¶
Add the following code to ExampleButtonView.cpp
:
namespace bdn::detail
{
VIEW_CORE_REGISTRY_IMPLEMENTATION(ExampleButtonView)
}
ExampleButtonView::ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory)
: View(viewCoreFactory)
{
bdn::detail::VIEW_CORE_REGISTER(ExampleButtonView, View::viewCoreFactory());
}
ExampleButtonView.cpp
#include <bdn/ExampleButtonView.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_IMPLEMENTATION(ExampleButtonView)
}
ExampleButtonView::ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory)
: View(viewCoreFactory)
{
bdn::detail::VIEW_CORE_REGISTER(ExampleButtonView, View::viewCoreFactory());
}
}
View Core Implementation¶
Add the following code to ExampleButtonViewCore.mm
:
namespace bdn::detail
{
CORE_REGISTER(ExampleButtonView,
bdn::ios::ExampleButtonViewCore,
ExampleButtonView)
}
ExampleButtonViewCore.mm
#import <bdn/ios/ExampleButtonViewCore.hh>
@interface ExampleUIButton : UIButton <UIViewWithFrameNotification>
@property(nonatomic, assign) std::weak_ptr<bdn::ios::ExampleButtonViewCore> core;
@end
@implementation ExampleUIButton
- (void)setViewCore:(std::weak_ptr<bdn::ios::ViewCore>)viewCore
{
self.core = std::dynamic_pointer_cast<bdn::ios::ExampleButtonViewCore>(viewCore.lock());
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (auto core = self.core.lock()) {
core->frameChanged();
}
}
@end
namespace bdn::detail
{
CORE_REGISTER(ExampleButtonView, bdn::ios::ExampleButtonViewCore, ExampleButtonView)
}
namespace bdn::ios
{
ExampleButtonViewCore::ExampleButtonViewCore(const std::shared_ptr<bdn::ViewCoreFactory> &viewCoreFactory)
: ViewCore(
viewCoreFactory,
[ExampleUIButton buttonWithType:UIButtonTypeSystem])
{
}
ExampleButtonViewCore::~ExampleButtonViewCore()
{
}
}
Adding Properties¶
To represent properties of a view, use the Property
class.
As an example, add a label
property so users of your view can set the button's label.
class ExampleButtonView : public ui::View
{
public:
Property<std::string> label;
}
ExampleButtonView.h
#pragma once
#include <bdn/ui/View.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
class ExampleButtonView : public ui::View
{
public:
Property<std::string> label;
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
class Core
{
};
};
}
To connect the property to the core, replicate the property in the Core
interface:
class Core
{
public:
Property<std::string> label;
}
ExampleButtonView.h
#pragma once
#include <bdn/ui/View.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
class ExampleButtonView : public ui::View
{
public:
Property<std::string> label;
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
class Core
{
public:
Property<std::string> label;
};
};
}
To bind the two label properties in View
and Core
properties, override the View::bindViewCore()
function:
void ExampleButtonView::bindViewCore()
{
View::bindViewCore();
auto buttonCore = core<ExampleButtonView::Core>();
buttonCore->label.bind(label);
}
ExampleButtonView.cpp
#include <bdn/ExampleButtonView.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_IMPLEMENTATION(ExampleButtonView)
}
ExampleButtonView::ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory)
: View(viewCoreFactory)
{
bdn::detail::VIEW_CORE_REGISTER(ExampleButtonView, View::viewCoreFactory());
}
void ExampleButtonView::bindViewCore()
{
View::bindViewCore();
auto buttonCore = core<ExampleButtonView::Core>();
buttonCore->label.bind(label);
}
}
Now it is easy to react to changes to label
in the iOS Implementation by adding an onChange()
handler in the view core's init function.
namespace bdn::ios {
void ExampleButtonViewCore::init()
{
ViewCore::init();
label.onChange() += [=](auto newValue) {
auto uiButton = (UIButton*)uiView();
[uiButton setTitle:bdn::fk::stringToNSString(value) forState:UIControlStateNormal]];
};
}
}
ExampleButtonViewCore.mm
#import <bdn/ios/ExampleButtonViewCore.hh>
#import <bdn/foundationkit/stringUtil.hh>
@interface ExampleUIButton : UIButton <UIViewWithFrameNotification>
@property(nonatomic, assign) std::weak_ptr<bdn::ios::ExampleButtonViewCore> core;
@end
@implementation ExampleUIButton
- (void)setViewCore:(std::weak_ptr<bdn::ios::ViewCore>)viewCore
{
self.core = std::dynamic_pointer_cast<bdn::ios::ExampleButtonViewCore>(viewCore.lock());
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (auto core = self.core.lock()) {
core->frameChanged();
}
}
@end
namespace bdn::detail
{
CORE_REGISTER(ExampleButtonView, bdn::ios::ExampleButtonViewCore, ExampleButtonView)
}
namespace bdn::ios
{
ExampleButtonViewCore::ExampleButtonViewCore(const std::shared_ptr<bdn::ViewCoreFactory> &viewCoreFactory)
: ViewCore(
viewCoreFactory,
[ExampleUIButton buttonWithType:UIButtonTypeSystem])
{
}
ExampleButtonViewCore::~ExampleButtonViewCore()
{
}
void ExampleButtonViewCore::init()
{
ViewCore::init();
label.onChange() += [=](auto newValue) {
auto uiButton = (UIButton*)uiView();
[uiButton setTitle:bdn::fk::stringToNSString(value) forState:UIControlStateNormal]];
};
}
}
Sending Notifications to the View¶
To allow users of your new class to react to button clicks, you can use a combination of a Notifier and a WeakCallback
.
First, add the WeakCallback
to your Core:
class ExampleButtonView : public ui::View
{
public:
class Core
{
public:
WeakCallback<void()> _clickCallback;
}
}
ExampleButtonView.h
#pragma once
#include <bdn/ui/View.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
class ExampleButtonView : public ui::View
{
public:
Property<std::string> label;
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
class Core
{
public:
Property<std::string> label;
public:
WeakCallback<void()> _clickCallback;
};
};
}
Now you can fire the callback from your UIButton
subclass:
- (void)clicked
{
if (auto core = self.core.lock()) {
core->_clickCallback.fire();
}
}
ExampleButtonViewCore.mm
#import <bdn/ios/ExampleButtonViewCore.hh>
#import <bdn/foundationkit/stringUtil.hh>
@interface ExampleUIButton : UIButton <UIViewWithFrameNotification>
@property(nonatomic, assign) std::weak_ptr<bdn::ios::ExampleButtonViewCore> core;
@end
@implementation ExampleUIButton
- (void)setViewCore:(std::weak_ptr<bdn::ios::ViewCore>)viewCore
{
self.core = std::dynamic_pointer_cast<bdn::ios::ExampleButtonViewCore>(viewCore.lock());
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (auto core = self.core.lock()) {
core->frameChanged();
}
}
- (void)clicked
{
if (auto core = self.core.lock()) {
core->_clickCallback.fire();
}
}
@end
namespace bdn::detail
{
CORE_REGISTER(ExampleButtonView, bdn::ios::ExampleButtonViewCore, ExampleButtonView)
}
namespace bdn::ios
{
ExampleButtonViewCore::ExampleButtonViewCore(const std::shared_ptr<bdn::ViewCoreFactory> &viewCoreFactory)
: ViewCore(
viewCoreFactory,
[ExampleUIButton buttonWithType:UIButtonTypeSystem])
{
}
ExampleButtonViewCore::~ExampleButtonViewCore()
{
}
void ExampleButtonViewCore::init()
{
ViewCore::init();
label.onChange() += [=](auto newValue) {
auto uiButton = (UIButton*)uiView();
[uiButton setTitle:bdn::fk::stringToNSString(value) forState:UIControlStateNormal]];
};
}
}
Add the notifier and the callback receiver to your View
class:
class ExampleButtonView
{
public:
Notifier<const ClickEvent &> _onClick;
WeakCallback<void()>::Receiver _clickCallbackReceiver;
}
ExampleButtonView.h
#pragma once
#include <bdn/ui/View.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_DECLARATION(ExampleButtonView)
}
class ExampleButtonView : public ui::View
{
public:
Property<std::string> label;
public:
ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory = nullptr);
public:
Notifier<const ClickEvent &> _onClick;
WeakCallback<void()>::Receiver _clickCallbackReceiver;
public:
class Core
{
public:
Property<std::string> label;
public:
WeakCallback<void()> _clickCallback;
};
};
}
Finally, connect it to the WeakCallback
:
void ExampleButtonView::bindViewCore()
{
_clickCallbackReceiver = buttonCore->_clickCallback.set([=](){
ClickEvent evt(shared_from_this());
_onClick.notify(evt);
});
}
ExampleButtonView.cpp
#include <bdn/ExampleButtonView.h>
namespace bdn
{
namespace detail
{
VIEW_CORE_REGISTRY_IMPLEMENTATION(ExampleButtonView)
}
ExampleButtonView::ExampleButtonView(
std::shared_ptr<ViewCoreFactory> viewCoreFactory)
: View(viewCoreFactory)
{
bdn::detail::VIEW_CORE_REGISTER(ExampleButtonView, View::viewCoreFactory());
}
void ExampleButtonView::bindViewCore()
{
View::bindViewCore();
auto buttonCore = core<ExampleButtonView::Core>();
buttonCore->label.bind(label);
_clickCallbackReceiver = buttonCore->_clickCallback.set([=](){
ClickEvent evt(shared_from_this());
_onClick.notify(evt);
});
}
}
Congratulations, your first custom view should be working now!