Question

I've been searching for the past few hours and I cannot figure this out. I'm creating a titanium module for iOS in Obj-C. The module compiles fine. My test project can see the module, however, I keep getting this error:

Invalid method (createView) passed to TiVolumesliderModule at app.js

My app.js contains

var VolumeSlider = require('ti.volumeslider'); //-- this works
Titanium.API.info("module is => "+VolumeSlider); //-- this works: module is => [object TiVolumesliderModule]

var volumeSlider = VolumeSlider.createView({
  width: '90%',
  height: 'auto',
  color: '#000',
  bottom: 10,
});

My Obj-C files are below. I am not too familiar with Obj-C so I apologize for posting these long files.

TiVolumesliderViewProxy.h

#import "TiViewProxy.h"

@interface TiVolumesliderViewProxy : TiViewProxy {

}

@end

TiVolumesliderViewProxy.m

#import "TiVolumesliderViewProxy.h"
#import "TiVolumesliderView.h"

NSArray* sliderKeySequence;

@implementation TiVolumesliderViewProxy

-(NSArray *)keySequence
{
    if (sliderKeySequence == nil)
    {
        sliderKeySequence = [[NSArray arrayWithObjects:@"value",nil] retain];
    }
    return sliderKeySequence;
}

-(UIViewAutoresizing)verifyAutoresizing:(UIViewAutoresizing)suggestedResizing
{
    return suggestedResizing & ~UIViewAutoresizingFlexibleHeight;
}

USE_VIEW_FOR_VERIFY_HEIGHT

@end

TiVolumesliderView.h

#import "TiUIView.h"
#import <MediaPlayer/MediaPlayer.h>

@interface TiVolumesliderView : TiUIView<LayoutAutosizing> {
@private
    MPVolumeView *sliderView;
    UISlider *volumeViewSlider;
    NSDate* lastTouchUp;
    NSTimeInterval lastTimeInterval;
}

- (IBAction)sliderChanged:(id)sender;

@end

TiVolumesliderView.m

#import "TiVolumesliderView.h"
#import "TiVolumesliderViewProxy.h"
#import "TiApp.h"
#import "TiUtils.h"

@implementation TiVolumesliderView

-(void)dealloc
{
    [volumeViewSlider removeTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
    RELEASE_TO_NIL(sliderView);
    RELEASE_TO_NIL(lastTouchUp);
    [super dealloc];
}

-(MPVolumeView*)sliderView
{
    if (sliderView==nil)
    {
        sliderView = [[MPVolumeView alloc] initWithFrame:[self bounds]];
        [sliderView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
        [self addSubview:sliderView];

        for (UIView *view in [sliderView subviews]) {
            if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
                volumeViewSlider = (UISlider *) view;
            }
        }

        [volumeViewSlider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
        lastTouchUp = [[NSDate alloc] init];
        lastTimeInterval = 1.0; // Short-circuit so that we don't ignore the first fire
    }
    return sliderView;
}

-(BOOL)hasTouchableListener
{
    // since this guy only works with touch events, we always want them
    // just always return YES no matter what listeners we have registered
    return YES;
}

-(void)setThumb:(id)value forState:(UIControlState)state
{
    [volumeViewSlider setThumbImage:[TiUtils image:value proxy:[self proxy]] forState:state];
}

-(void)setRightTrack:(id)value forState:(UIControlState)state
{
    [volumeViewSlider setMaximumTrackImage:[TiUtils stretchableImage:value proxy:[self proxy]] forState:state];
}

-(void)setLeftTrack:(id)value forState:(UIControlState)state
{
    [volumeViewSlider setMinimumTrackImage:[TiUtils stretchableImage:value proxy:[self proxy]] forState:state];
}

#pragma mark View controller stuff

-(void)setThumbImage_:(id)value
{
    [self setThumb:value forState:UIControlStateNormal];
}

-(void)setSelectedThumbImage_:(id)value
{
    [self setThumb:value forState:UIControlStateSelected];
}

-(void)setHighlightedThumbImage_:(id)value
{
    [self setThumb:value forState:UIControlStateHighlighted];
}

-(void)setDisabledThumbImage_:(id)value
{
    [self setThumb:value forState:UIControlStateDisabled];
}

-(void)setLeftTrackImage_:(id)value
{
    [self setLeftTrack:value forState:UIControlStateNormal];
}

-(void)setSelectedLeftTrackImage_:(id)value
{
    [self setLeftTrack:value forState:UIControlStateSelected];
}

-(void)setHighlightedLeftTrackImage_:(id)value
{
    [self setLeftTrack:value forState:UIControlStateHighlighted];
}

-(void)setDisabledLeftTrackImage_:(id)value
{
    [self setLeftTrack:value forState:UIControlStateDisabled];
}

-(void)setRightTrackImage_:(id)value
{
    [self setRightTrack:value forState:UIControlStateNormal];
}

-(void)setSelectedRightTrackImage_:(id)value
{
    [self setRightTrack:value forState:UIControlStateSelected];
}

-(void)setHighlightedRightTrackImage_:(id)value
{
    [self setRightTrack:value forState:UIControlStateHighlighted];
}

-(void)setDisabledRightTrackImage_:(id)value
{
    [self setRightTrack:value forState:UIControlStateDisabled];
}

-(void)setValue_:(id)value withObject:(id)properties
{   
    CGFloat newValue = [TiUtils floatValue:value];

    if (newValue > 1) 
    {
        newValue = 1;
    }
    else if (newValue < 0)
    {
        newValue = 0;
    }

    BOOL animated = [TiUtils boolValue:@"animated" properties:properties def:NO];
    UISlider * ourSlider = volumeViewSlider;
    [ourSlider setValue:newValue animated:animated];
    [[MPMusicPlayerController applicationMusicPlayer] setVolume:newValue];
    [self sliderChanged:ourSlider];
}

-(void)setValue_:(id)value
{
    [self setValue_:value withObject:nil];
}

-(void)setEnabled_:(id)value
{
    [volumeViewSlider setEnabled:[TiUtils boolValue:value]];
}

-(CGFloat)verifyHeight:(CGFloat)suggestedHeight
{
    CGSize fitSize = [[self sliderView] sizeThatFits:CGSizeZero];
    return fitSize.height;
}

USE_PROXY_FOR_VERIFY_AUTORESIZING

#pragma mark Delegates 

- (IBAction)sliderChanged:(id)sender
{
    NSNumber * newValue = [NSNumber numberWithFloat:[(UISlider *)sender value]];
    [self.proxy replaceValue:newValue forKey:@"value" notification:NO];

    if ([self.proxy _hasListeners:@"change"])
    {
        [self.proxy fireEvent:@"change" withObject:[NSDictionary dictionaryWithObject:newValue forKey:@"value"]];
    }
}

@end

TiVolumesliderModule.h

#import "TiModule.h"
#import <MediaPlayer/MediaPlayer.h>

@interface TiVolumesliderModule : TiModule 
{
}

@end

TiVolumesliderModule.m

#import "TiVolumesliderModule.h"
#import "TiApp.h"
#import "TiBase.h"
#import "TiHost.h"
#import "TiUtils.h"

@implementation TiVolumesliderModule

#pragma mark Internal

// this is generated for your module, please do not change it
-(id)moduleGUID
{
    return @"56141681-6e15-4783-a284-e4aa93444757";
}

// this is generated for your module, please do not change it
-(NSString*)moduleId
{
    return @"ti.volumeslider";
}

#pragma mark Lifecycle

-(void)startup
{
    // this method is called when the module is first loaded
    // you *must* call the superclass
    [super startup];

    NSLog(@"[INFO] %@ loaded",self);
}

-(void)shutdown:(id)sender
{
    // this method is called when the module is being unloaded
    // typically this is during shutdown. make sure you don't do too
    // much processing here or the app will be quit forceably

    // you *must* call the superclass
    [super shutdown:sender];
}

#pragma mark Cleanup 

-(void)dealloc
{
    // release any resources that have been retained by the module
    [super dealloc];
}

#pragma mark Internal Memory Management

-(void)didReceiveMemoryWarning:(NSNotification*)notification
{
    // optionally release any resources that can be dynamically
    // reloaded once memory is available - such as caches
    [super didReceiveMemoryWarning:notification];
}

#pragma mark Listener Notifications

-(void)_listenerAdded:(NSString *)type count:(int)count
{
    if (count == 1 && [type isEqualToString:@"my_event"])
    {
        // the first (of potentially many) listener is being added 
        // for event named 'my_event'
    }
}

-(void)_listenerRemoved:(NSString *)type count:(int)count
{
    if (count == 0 && [type isEqualToString:@"my_event"])
    {
        // the last listener called for event named 'my_event' has
        // been removed, we can optionally clean up any resources
        // since no body is listening at this point for that event
    }
}

#pragma Public APIs

-(id)example:(id)args
{
    // example method
    return @"hello world";
}

-(id)exampleProp
{
    // example property getter
    return @"hello world";
}

-(void)exampleProp:(id)value
{
    // example property setter
}


-(id)moduleVersion
{
    // example property getter
    return @"0.1";
}

@end
Was it helpful?

Solution

Based on past experience, this could be caused by some different things.

The case and prefix of your file names matters. I see some inconsistency with the view and view proxy. Make sure everything has this prefix: TiVolumeslider (with a lowercase s), followed by Module or View or ViewProxy. Titanium does some magic to associate the ViewProxies with the Module, and the View with the ViewProxy.

Next, make sure that the view and view proxy .h and .m files have been added to the Xcode project. Open TiVolumeslider.xcodeproj in Xcode, and drag the 4 view/view proxy .h and .m files in to the Classes group. It'll ask if you want to add them to the various targets, which you do, so leave the defaults, add the files, and then try again.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top