I'm doing most of my watch app UI in code and would like to configure row controller's IB properties at load time, before I access it with rowController(at:).
Is there an easy way to do it? All IB properties are nil at init(), and awakeFromNib() is not available for NSObject in WatchKit.
Your IBOutlets must be already bound to your custom table row class which is used in your Interface storyboard. In the custom row class you can update the interface controls by setting the object keeping the information to display.
class TableMainCellType: NSObject {
#IBOutlet weak var label:WKInterfaceLabel!
#IBOutlet weak var group:WKInterfaceGroup!
var item:MyTableItem? {
didSet{
label.setText(item?.title)
group.setBackgroundColor(item?.color)
}
}
}
To update the table cells I would iterate in the function to update the table view in the InterfaceController like below.
// ...
for index in tableItems.indices{
if let row = self.table.rowController(at: index) as? TableMainCellType {
let item = tableItems[index]
row.item = item
}
}
// ...
Related
I'm creating a new watchOS app using SwiftUI and Combine trying to use a MVVM architecture, but when my viewModel changes, I can't seem to get a Text view to update in my View.
I'm using watchOS 6, SwiftUI and Combine. I am using #ObservedObject and #Published when I believe they should be used, but changes aren't reflected like I would expect.
// Simple ContentView that will push the next view on the navigation stack
struct ContentView: View {
var body: some View {
NavigationLink(destination: NewView()) {
Text("Click Here")
}
}
}
struct NewView: View {
#ObservedObject var viewModel: ViewModel
init() {
viewModel = ViewModel()
}
var body: some View {
// This value never updates
Text(viewModel.str)
}
}
class ViewModel: NSObject, ObservableObject {
#Published var str = ""
var count = 0
override init() {
super.init()
// Just something that will cause a property to update in the viewModel
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.count += 1
self?.str = "\(String(describing: self?.count))"
print("Updated count: \(String(describing: self?.count))")
}
}
}
Text(viewModel.str) never updates, even though the viewModel is incrementing a new value ever 1.0s. I have tried objectWillChange.send() when the property updates, but nothing works.
Am I doing something completely wrong?
For the time being, there is a solution that I luckily found out just by experimenting. I'm yet to find out what the actual reason behind this. Until then, you just don't inherit from NSObject and everything should work fine.
class ViewModel: ObservableObject {
#Published var str = ""
var count = 0
init() {
// Just something that will cause a property to update in the viewModel
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.count += 1
self?.str = "\(String(describing: self?.count))"
print("Updated count: \(String(describing: self?.count))")
}
}
}
I've tested this and it works.
A similar question also addresses this issue of the publisher object being a subclass of NSObject. So you may need to rethink if you really need an NSObject subclass or not. If you can't get away from NSObject, I recommend you try one of the solutions from the linked question.
I need to share a string with all the pages from the first page.
I was trying to do this with didDeactive() but its not working.
Is it possible to even do this?
It's a bit difficult to tell what you are really trying to do, so I'm going to infer a bit. I "think" you are trying to set some shared value from the first page and be able to use that value in other pages. If that's the case, then you could do something like the following:
class SharedData {
var value1 = "some initial value"
var value2 = "some other initial value"
class var sharedInstance: SharedData {
struct Singleton { static let instance = SharedData() }
return Singleton.instance
}
private init() {
// No-op
}
}
class Page1InterfaceController: WKInterfaceController {
func buttonTapped() {
SharedData.sharedInstance.value1 = "Something new that the others care about"
}
}
class Page2InterfaceController: WKInterfaceController {
#IBOutlet var label: WKInterfaceLabel!
override func willActivate() {
super.willActivate()
self.label.setText(SharedData.sharedInstance.value1)
}
}
The SharedData object is a singleton class that is a global object. Anyone can access it from anywhere. In the small example I provided, the Page1InterfaceController is handling a buttonTapped event and changing the value property of the SharedData instance to a new value. Then, when swiping to Page2InterfaceController, the new value is being displayed in label.
This is a super simple example of sharing data between objects. Hopefully that helps get you moving in the right direction.
I am using Knockout 3.2 and the new component system. I am trying to have components that include sub-components.
Home Page (component - with HomePageViewModel)
NewsFeed1 (component with HomePageViewModel.NewsFeedViewModel1)
NewsFeed2 (component with HomePageViewModel.NewsFeedViewModel2)
HomePageViewModel
var viewModel = (function () {
function viewModel() {
this.message = ko.observable("Welcome to DKT!");
this.newsFeedViewModel = new gr.viewModel();
this.newsFeedViewModel2 = new gr.viewModel();
this.newsFeedViewModel.message("Message 1");
this.newsFeedViewModel2.message("Message 2");
}
return viewModel;
})();
NewsFeedViewModel
var viewModel = (function () {
function viewModel() {
this.message = ko.observable("This is the profile!");
}
return viewModel;
})();
As you can see the HomePageViewModel contains both the NewsFeedViewModel. I now want to be able to use these as the DataContext/BindingContext of my two components but this does not seem to work.
Home.html
<news-feed data-bind="newsFeedViewModel"></news-feed>
<news-feed data-bind="newsFeedViewModel2"></news-feed>
Both these components do not use the ViewModels from the HomePageViewModel but uses a new NewsFeedViewModel. How can I make the datacontext of both these components bind to the viewModels stored in the top component (home)?
Generally, you would want to supply your component with any data via params. For example, with your structure, you could create the component like:
ko.components.register("news-feed", {
viewModel: function (params) {
this.vm = params.vm;
},
template: "<h2>News Feed</h2><div data-bind=\"text: vm.message\"></div>"
});
Then, you would define the elements like:
<news-feed params="vm: newsFeedViewModel"></news-feed>
<news-feed params="vm: newsFeedViewModel2"></news-feed>
You could choose to pass the message in directly for each and/or choose whatever names make sense for your params (rather than vm).
Sample: http://jsfiddle.net/rniemeyer/fssXE/
I'm trying to use Ember to dynamically create child views in a ContainerView.
The problem is those child views need data bindings to a value from an attribute given to the container view.
Here is a bit of code showing roughly what I'm doing:
ReferenceCat.EditorView = Ember.ContainerView.extend({
init: function(){
this._super();
if(this.get('model.type') != undefined){
this.modelTypeChanges();
}
},
modelTypeChanges : function(){
// 1st step: Remove all children of my view
this.removeAllChildren();
var model = this.get('model');
// 2nd step is to run through all of the type information
// generating the views that we need to actually
// fill in this reference
var tI = model.get('typeInfo');
var self = this;
tI.forEach(function(field){
// Now we have a field
switch(field.type){
case "string":
// add new child view here with data binding to data.<field.name>
break;
}
});
}
});
And this class is referenced like this:
{{view ReferenceCat.EditorView
modelBinding=model}}
In your modelTypeChanges function instead of using a switch statement to create the different types of childViews you need to override the ContainerView's createChildView function (http://emberjs.com/api/classes/Ember.ContainerView.html#method_createChildView). The createChildView function itself will return the instantiated childView and in that overidded function itself you can place your switch statement. So it will look something like this...
createChildView: function(view, attrs) {
switch(attr.type) {
case "string":
view = yourview //your view class here, it is overriding the default
break;
}
.....
...
//Once you place all your case statements above
//make sure to call the parents createChildView function.
//This line is actually creating the view you want with the
//properties that are defined in the object attrs
return this._super(view, attrs);
}
So make sure when you call your overridden createChildView function to pass it the object you want bounded in the childView as a property of the object you pass as its second parameter...
var self = this,
tempviewarray = [];
tI.forEach(function(field){
var view = self.createChildView(null, field);
tempviewarray.push(view);
});
// add all the instantiated views to the ContainerView as children views
this.pushObjects(tempviewarray);
I'm trying to make annotations on an image.
I want to store all these annotations into a collection so that I can later remove or edit particular note(annotation).
I'm creating a UIComponent instance for example markUp and doing
markUp.graphics.lineStyle(2, drawColor);
markUp.graphics.moveTo(x1, y1);
markUp.graphics.lineTo(x2, y2);
I want to create a UIComponent instance every time whenever user makes a new annotation on my image.
How to generate dynamic instance and how to save them to a collection. Any help is appreciated.
From what I understand after a chat with Kishor:
Users may mark specific areas by mouse drag and add a comment accordingly. Markup and comment are stored in the database.
There is only one image enlarged at a time, and this image should display all markups. Clicking on a markup will raise a popup showing the associated comment.
Apparently drawing the markup is rather expensive, so once drawn a particular markup should be reused when the user switchs between images. The question is now:
How to reuse markups displayed on an image?
Answer:
Use an Object or Dictionary where you create one entry per image and the entry points to an Array of markups. You may create a convenient class to encapsulate the functionality of this map:
package {
import flash.display.DisplayObject;
public class Markups {
private var _map : Object;
public function Markups() {
_map = new Object();
}
public function addMarkup(imageId : String, markup : DisplayObject) : void {
if (!_map[imageId]) _map[imageId] = new Array();
(_map[imageId] as Array).push(markup);
}
public function hasMarkups(imageId : String) : Boolean {
return _map[imageId] !== undefined;
}
public function getMarkups(imageId : String) : Array {
return _map[imageId];
}
public function removeMarkup(imageId : String, markup : DisplayObject) : void {
var markups : Array = _map[imageId];
var index : int = markups.indexOf(markup);
markups.splice(index, 1);
if (!markups.length) delete _map[imageId];
}
}
}
Store this map at a place that is accessible from your image container.
var markupsPerImage : Markups = new Markups();
Add or remove a markup like this:
markupsPerImage.addMarkup(myImage.id, markupView);
markupsPerImage.removeMarkup(myImage.id, markupView);
Whenever the user changes the current image you 1. cleanup all markups of the last and 2. add all markups stored in the map.
In the image container:
public function setImage(myImage : MyImage) : void {
// remove markups
while (markupContainer.numChildren) {
markupContainer.removeChildAt(0);
}
// add markups
var markups : Array = markupsPerImage.getMarkups(myImage.id);
var markup : MarkupView;
foreach (markups as markup) {
markupContainer.addChild(markup);
}
}
Notes:
The example code assumes you have a markup container that hosts all markups. This container should have a reference to the markup map.