I am creating a simple Texture display that essentially renders the Video frames in BGRA format through Metal display. I follow the same steps as told in Metal WWDC session. But I have problems in creating the render encoder. My code is
id <MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
id<MTLLibrary> library = [device newDefaultLibrary];
// Create Render Command Descriptor.
MTLRenderPipelineDescriptor* renderPipelineDesc = [MTLRenderPipelineDescriptor new];
renderPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
renderPipelineDesc.vertexFunction = [library newFunctionWithName:#"basic_vertex"];
renderPipelineDesc.fragmentFunction = [library newFunctionWithName:#"basic_fragment"];
NSError* error = nil;
id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:renderPipelineDesc
error:&error];
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
MTLRenderPassDescriptor* renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
id<CAMetalDrawable> drawable = [_metalLayer nextDrawable];
MTLRenderPassColorAttachmentDescriptor* colorAttachmentDesc = [MTLRenderPassColorAttachmentDescriptor new];
colorAttachmentDesc.texture = drawable.texture;
colorAttachmentDesc.loadAction = MTLLoadActionLoad;
colorAttachmentDesc.storeAction = MTLStoreActionStore;
colorAttachmentDesc.clearColor = MTLClearColorMake(0, 0, 0, 1);
[renderPassDesc.colorAttachments setObject:colorAttachmentDesc atIndexedSubscript:0];
[inTexture replaceRegion:region
mipmapLevel:0
withBytes:imageBytes
bytesPerRow:CVPixelBufferGetBytesPerRow(_image)];
id<MTLRenderCommandEncoder> renderCmdEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDesc];
[renderCmdEncoder setRenderPipelineState:_renderPipelineState];
[renderCmdEncoder endEncoding];
This code crashes in the line saying "No Render Targets Found"
id renderCmdEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDesc];
I am not able to figure out where and how to set the render target.
This will work perfectly; if you need help implementing it, let me know:
#import UIKit;
#import AVFoundation;
#import CoreMedia;
#import <MetalKit/MetalKit.h>
#import <Metal/Metal.h>
#import <MetalPerformanceShaders/MetalPerformanceShaders.h>
#interface ViewController : UIViewController <MTKViewDelegate, AVCaptureVideoDataOutputSampleBufferDelegate> {
NSString *_displayName;
NSString *serviceType;
}
#property (retain, nonatomic) SessionContainer *session;
#property (retain, nonatomic) AVCaptureSession *avSession;
#end;
#import "ViewController.h"
#interface ViewController () {
MTKView *_metalView;
id<MTLDevice> _device;
id<MTLCommandQueue> _commandQueue;
id<MTLTexture> _texture;
CVMetalTextureCacheRef _textureCache;
}
#property (strong, nonatomic) AVCaptureDevice *videoDevice;
#property (nonatomic) dispatch_queue_t sessionQueue;
#end
#implementation ViewController
- (void)viewDidLoad {
NSLog(#"%s", __PRETTY_FUNCTION__);
[super viewDidLoad];
_device = MTLCreateSystemDefaultDevice();
_metalView = [[MTKView alloc] initWithFrame:self.view.bounds];
[_metalView setContentMode:UIViewContentModeScaleAspectFit];
_metalView.device = _device;
_metalView.delegate = self;
_metalView.clearColor = MTLClearColorMake(1, 1, 1, 1);
_metalView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
_metalView.framebufferOnly = NO;
_metalView.autoResizeDrawable = NO;
CVMetalTextureCacheCreate(NULL, NULL, _device, NULL, &_textureCache);
[self.view addSubview:_metalView];
self.sessionQueue = dispatch_queue_create( "session queue", DISPATCH_QUEUE_SERIAL );
if ([self setupCamera]) {
[_avSession startRunning];
}
}
- (BOOL)setupCamera {
NSLog(#"%s", __PRETTY_FUNCTION__);
#try {
NSError * error;
_avSession = [[AVCaptureSession alloc] init];
[_avSession beginConfiguration];
[_avSession setSessionPreset:AVCaptureSessionPreset640x480];
// get list of devices; connect to front-facing camera
self.videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (self.videoDevice == nil) return FALSE;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice error:&error];
[_avSession addInput:input];
dispatch_queue_t sampleBufferQueue = dispatch_queue_create("CameraMulticaster", DISPATCH_QUEUE_SERIAL);
AVCaptureVideoDataOutput * dataOutput = [[AVCaptureVideoDataOutput alloc] init];
[dataOutput setAlwaysDiscardsLateVideoFrames:YES];
[dataOutput setVideoSettings:#{(id)kCVPixelBufferPixelFormatTypeKey: #(kCVPixelFormatType_32BGRA)}];
[dataOutput setSampleBufferDelegate:self queue:sampleBufferQueue];
[_avSession addOutput:dataOutput];
[_avSession commitConfiguration];
} #catch (NSException *exception) {
NSLog(#"%s - %#", __PRETTY_FUNCTION__, exception.description);
return FALSE;
} #finally {
return TRUE;
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
{
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
CVMetalTextureRef texture = NULL;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &texture);
if(status == kCVReturnSuccess)
{
_metalView.drawableSize = CGSizeMake(width, height);
_texture = CVMetalTextureGetTexture(texture);
_commandQueue = [_device newCommandQueue];
CFRelease(texture);
}
}
}
- (void)drawInMTKView:(MTKView *)view {
// creating command encoder
if (_texture) {
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
id<MTLTexture> drawingTexture = view.currentDrawable.texture;
// set up and encode the filter
MPSImageGaussianBlur *filter = [[MPSImageGaussianBlur alloc] initWithDevice:_device sigma:5];
[filter encodeToCommandBuffer:commandBuffer sourceTexture:_texture destinationTexture:drawingTexture];
// committing the drawing
[commandBuffer presentDrawable:view.currentDrawable];
[commandBuffer commit];
_texture = nil;
}
}
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
}
#end
you should try one of following points
1.Instead of creating new render pass descriptor,use current render pass descriptor object from MTKView object.this render pass descriptor already will be configured.you need not set anything.try the sample code given below-
if let currentPassDesc = view.currentRenderPassDescriptor,
let currentDrawable = view.currentDrawable
{
let renderCommandEncoder =
commandBuffer.makeRenderCommandEncoder(descriptor: currentPassDesc)
renderCommandEncoder.setRenderPipelineState(renderPipeline)
//set vertex buffers and call draw apis
.......
.......
commandBuffer.present(currentDrawable)
}
2.you are creating a new render pass descriptor and then setting its color attachment by the texture of drawable object so instead of doing this you should create a new texture object and then set usage of this texture as render target.then you will get content rendered in your new texture but it will be not displayed on screen so to get displayed the content of your textue you have to copy the content of your texture in drawable texture and then present drawable.
below is the code of making render target -
renderPassDescriptor.colorAttachments[0].clearColor =
MTLClearColor(red:
0.0,green: 0.0,blue: 0.0,alpha: 1.0)
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].storeAction = .store
renderPassDescriptor.depthAttachment.clearDepth = 1.0
renderPassDescriptor.depthAttachment.loadAction = .clear
renderPassDescriptor.depthAttachment.storeAction = .dontCare
let view = self.view as!MTKView
let textDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat:
.bgra8Unorm, width: Int(view.frame.width),
height: Int(view.frame.height), mipmapped: false)
textDesc.depth = 1
//see below line
textDesc.usage =
[MTLTextureUsage.renderTarget,MTLTextureUsage.shaderRead]
textDesc.storageMode = .private
mainPassFrameBuffer = device.makeTexture(descriptor: textDesc)
renderPassDescriptor.colorAttachments[0].texture = mainPassFrameBuffer
Related
I am sorry for the long post but I am at my wits end and have been stumped for days over this. Here's the scenario. My app loads a response from core data, converts the values to NSStrings so that I can add them to an NSDictionary. Then the NSDictionary is converted to NSData so I can attach it as a file to email. The purpose of this is so I can create a database of information including images, videos, etc. I was able to get everything to work except I am having an issue with an NSMutableArray. Here's the process:
I create an event and then load the data for exporting with this code.
EventDB *per = [[EventDB alloc]init];
per.customLayoutArray = [record.customLayoutArray description] ?
[record.customLayoutArray description] : #"";
NSDictionary *dict = [per dictionaryWithValuesForKeys:#[#"customLayoutArray"];
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL];
Then I email the data using MFMailComposer. Then I have a custom UTI that allows me the open the url from the email and then I import the data and load it into my coredata entity with this
if([[url pathExtension] isEqualToString:#"ipix"]) {
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TSPItem"
inManagedObjectContext:self.managedObjectContext];
TSPItem *record = (TSPItem *)[[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:self.managedObjectContext];
if (record) {
NSString *datetime = [jsonData objectForKey:#"customLayoutArray"];
record.customLayoutArray = [[datetime propertyList] mutableCopy];
}
That works fine. It does import the way I want but when I launch the event I get this crash message
** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSCFString apply]: unrecognized selector sent to instance 0x1c81a5f60
Now here's the code where it crashes.
NSMutableArray *archiveArray = self.record.customLayoutArray;
NSString *mycustom = [NSString stringWithFormat:#"%#_customlayout",
self.record.eventname];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:archiveArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:mycustom];
self.customLayoutArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(#"BOOTH EVENT ID %#", self.customLayoutArray);
[self.customLayoutArray makeObjectsPerformSelector:#selector(apply)];
This is the log from BOOTH EVENT ID
BOOTH EVENT ID (
"<Rectangle:0x102d38cb0self.scaleValue=1.842393\n, self.rotateValue=0.000000\n, self.width=368.478516\n, self.height=368.478516\n, self.radius=0\n, self.frame={{104, 113.5}, {200, 200}}\n, self.isApplied=NO\n>",
"<Rectangle:0x102d393c0self.scaleValue=1.000000\n, self.rotateValue=0.000000\n, self.width=200.000000\n, self.height=200.000000\n, self.radius=0\n, self.frame={{253, 273.5}, {200, 200}}\n, self.isApplied=NO\n>"
)
The app crashes here. Now if I load the original event on my iPad (the one that I didn't export) the app works perfect and the NSLog response for BOOTH EVENT ID is identical.
The "apply" section refers to this file.
#import "Rectangle.h"
#import "DeviceSize.h"
#implementation Rectangle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.clipsToBounds = YES;
self.userInteractionEnabled = YES;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:[NSNumber numberWithFloat:self.scaleValue] forKey:#"ScaleValue"];
[coder encodeObject:[NSNumber numberWithFloat:self.rotateValue] forKey:#"RotateValue"];
[coder encodeObject:[NSNumber numberWithFloat:self.width] forKey:#"Width"];
[coder encodeObject:[NSNumber numberWithFloat:self.height] forKey:#"Height"];
[coder encodeObject:[NSNumber numberWithInteger:self.radius] forKey:#"Radius"];
[coder encodeObject:[NSNumber numberWithBool:self.isApplied] forKey:#"isApplied"];
[coder encodeObject:self.image forKey:#"Image"];
[coder encodeObject:self.backgroundColor forKey:#"BackgroundColor"];
[coder encodeObject:[NSValue valueWithCGPoint:self.center] forKey:#"CenterPoint"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.scaleValue = [[coder decodeObjectForKey:#"ScaleValue"] floatValue];
self.rotateValue = [[coder decodeObjectForKey:#"RotateValue"] floatValue];
self.width = [[coder decodeObjectForKey:#"Width"] floatValue];
self.height = [[coder decodeObjectForKey:#"Height"] floatValue];
self.radius = [[coder decodeObjectForKey:#"Radius"] integerValue];
self.isApplied = [[coder decodeObjectForKey:#"isApplied"] boolValue];
[self.layer setCornerRadius:self.radius];
self.image = [coder decodeObjectForKey:#"Image"];
[self setBackgroundColor:[coder decodeObjectForKey:#"BackgroundColor"]];
//
if (self.width == self.height)
{
CGRect rect = CGRectMake(0, 0,200, 200);
self.frame = rect;
}
if (self.width > self.height)
{
CGRect rect = CGRectMake(0, 0,200, 150);
self.frame = rect;
}
if (self.width < self.height)
{
CGRect rect = CGRectMake(0, 0,150, 200);
self.frame = rect;
}
self.center = [[coder decodeObjectForKey:#"CenterPoint"] CGPointValue];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
/* Set UIView Border */
// Get the contextRef
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// Set the border width
CGContextSetLineWidth(contextRef, 5.0);
// Set the border color to RED
CGContextSetRGBStrokeColor(contextRef, 255.0, 0.0, 0.0, 1.0);
// Draw the border along the view edge
CGContextStrokeRect(contextRef, rect);
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (NSString *)description {
NSMutableString *description = [NSMutableString stringWithFormat:#"<%#:%p", NSStringFromClass([self class]), self];
[description appendFormat:#"self.scaleValue=%f\n", self.scaleValue];
[description appendFormat:#", self.rotateValue=%f\n", self.rotateValue];
[description appendFormat:#", self.width=%f\n", self.width];
[description appendFormat:#", self.height=%f\n", self.height];
[description appendFormat:#", self.radius=%li\n", (long)self.radius];
[description appendFormat:#", self.frame=%#\n", NSStringFromCGRect(self.frame)];
[description appendFormat:#", self.isApplied=%#\n", self.isApplied ? #"YES" : #"NO"];
[description appendString:#">"];
return description;
}
- (id)copyWithZone:(NSZone *)zone {
Rectangle *copy = [[[self class] allocWithZone:zone] init];
if (copy != nil) {
copy.scaleValue = self.scaleValue;
copy.rotateValue = self.rotateValue;
copy.width = self.width;
copy.height = self.height;
copy.radius = self.radius;
copy.frame = self.frame;
copy.isApplied = self.isApplied;
}
return copy;
}
#end
#implementation Rectangle(ApplyRotate)
#pragma mark -
- (Rectangle *)apply {
if (self.isApplied) {
return self;
}
Rectangle *rectangle = self;
CGPoint centerPoint = rectangle.center;
CGAffineTransform rotate = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rectangle.rotateValue));
CGAffineTransform scale = CGAffineTransformMakeScale(rectangle.scaleValue, rectangle.scaleValue);
CGAffineTransform scaleAndRotate = CGAffineTransformConcat(rotate, scale);
rectangle.transform = scaleAndRotate;
rectangle.center = centerPoint;
rectangle.isApplied = YES;
return rectangle;
}
#end
My goal is to mirror the screen of an iDevice to OSX, as lag-free as possible.
To my knowledge there are 2 ways to this:
Airplay Mirroring (e.g. Reflector)
CoreMediaIO via Lightning (e.g. Quicktime Recording)
I have chosen to pursue the second method, because (to my knowledge) connected iDevices can be recognized as DAL devices automatically after a one-time setup.
The main resource on how to do this is this blog: https://nadavrub.wordpress.com/2015/07/06/macos-media-capture-using-coremediaio/
That blog goes very deep into how to use CoreMediaIO, however it seems like you can work with AVFoundation once you have recognized the connected iDevice as an AVCaptureDevice.
This question: How to mirror iOS screen via USB? has posted a solution on how to grab each frame of the H264 (Annex B) muxxed datastream supplied by the iDevice.
However, my problem is that VideoToolbox will not correctly decode (Error Code -8969, BadData), even though there shouldn't be any difference in the code.
vtDecompressionDuctDecodeSingleFrame signalled err=-8969 (err) (VTVideoDecoderDecodeFrame returned error) at /SourceCache/CoreMedia_frameworks/CoreMedia-1562.240/Sources/VideoToolbox/VTDecompressionSession.c line 3241
Complete Code:
#import "ViewController.h"
#import CoreMediaIO;
#import AVFoundation;
#import AppKit;
#implementation ViewController
AVCaptureSession *session;
AVCaptureDeviceInput *newVideoDeviceInput;
AVCaptureVideoDataOutput *videoDataOutput;
- (void)viewDidLoad {
[super viewDidLoad];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
// Allow iOS Devices Discovery
CMIOObjectPropertyAddress prop =
{ kCMIOHardwarePropertyAllowScreenCaptureDevices,
kCMIOObjectPropertyScopeGlobal,
kCMIOObjectPropertyElementMaster };
UInt32 allow = 1;
CMIOObjectSetPropertyData( kCMIOObjectSystemObject,
&prop, 0, NULL,
sizeof(allow), &allow );
// Get devices
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed];
BOOL deviceAttahced = false;
for (int i = 0; i < [devices count]; i++) {
AVCaptureDevice *device = devices[i];
if ([[device uniqueID] isEqualToString:#"b48defcadf92f300baf5821923f7b3e2e9fb3947"]) {
deviceAttahced = true;
[self startSession:device];
break;
}
}
}
return self;
}
- (void) deviceConnected:(AVCaptureDevice *)device {
if ([[device uniqueID] isEqualToString:#"b48defcadf92f300baf5821923f7b3e2e9fb3947"]) {
[self startSession:device];
}
}
- (void) startSession:(AVCaptureDevice *)device {
// Init capturing session
session = [[AVCaptureSession alloc] init];
// Star session configuration
[session beginConfiguration];
// Add session input
NSError *error;
newVideoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (newVideoDeviceInput == nil) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSLog(#"%#", error);
});
} else {
[session addInput:newVideoDeviceInput];
}
// Add session output
videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
videoDataOutput.videoSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey: (id)kCVPixelBufferPixelFormatTypeKey];
dispatch_queue_t videoQueue = dispatch_queue_create("videoQueue", NULL);
[videoDataOutput setSampleBufferDelegate:self queue:videoQueue];
[session addOutput:videoDataOutput];
// Finish session configuration
[session commitConfiguration];
// Start the session
[session startRunning];
}
#pragma mark - AVCaptureAudioDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
//NSImage *resultNSImage = [self imageFromSampleBuffer:sampleBuffer];
//self.imageView.image = [self nsImageFromSampleBuffer:sampleBuffer];
self.imageView.image = [[NSImage alloc] initWithData:imageToBuffer(sampleBuffer)];
}
NSData* imageToBuffer( CMSampleBufferRef source) {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(source);
CVPixelBufferLockBaseAddress(imageBuffer,0);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
void *src_buff = CVPixelBufferGetBaseAddress(imageBuffer);
NSData *data = [NSData dataWithBytes:src_buff length:bytesPerRow * height];
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return data;
}
No, you must remove annex b start codes and replace them with size values. Same format as MP4
I am trying to make a vehicle (in this case a train) move based on player input. If I move the train via an SKAction, the wheels do not rotate. I could use the applyForce method on its physics body, but it I need more control. I need to make it move a certain distance over a certain amount of time. How can this be accomplished?
-(void)didMoveToView:(SKView *)view {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
SKSpriteNode *trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
trainBody.zPosition = 0;
trainBody.position = CGPointMake(300, 500);
trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:trainBody.size];
[self addChild:trainBody];
SKTexture *trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
SKSpriteNode *trainWheel1 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel1.zPosition = 1;
trainWheel1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel1.size.width/2];
trainWheel1.physicsBody.allowsRotation = YES;
trainWheel1.position = CGPointMake(220, 400);
[self addChild:trainWheel1];
SKSpriteNode *trainWheel2 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel2.zPosition = 1;
trainWheel2.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel2.size.width/2];
trainWheel2.physicsBody.allowsRotation = YES;
trainWheel2.position = CGPointMake(380, 400);
[self addChild:trainWheel2];
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[trainWheel1 addChild:dot];
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel1.physicsBody anchor:trainWheel1.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel2.physicsBody anchor:trainWheel2.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
//[trainWheel1 runAction:[SKAction moveByX:300 y:0 duration:3]];
[trainBody.physicsBody applyForce:CGVectorMake(3000, 0)];
}
UPDATE: Implemented With A Train Class (suggested by Jaffer Sheriff)
Train.h
#import <SpriteKit/SpriteKit.h>
#interface Train : SKSpriteNode
-(void) createPhysics;
-(void) moveLeft;
-(void) moveRight;
#end
Train.m
#import "Train.h"
#interface Train()
#property SKSpriteNode *trainBody, *trainWheelFront, *trainWheelRear;
#property SKPhysicsWorld *physicsWorld;
#end
#implementation Train
-(instancetype) init {
if (self = [super init]) {
[self initTrainBody];
[self initWheels];
}
return self;
}
-(void) initTrainBody {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
_trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
_trainBody.zPosition = 0;
_trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_trainBody.size];
[self addChild:_trainBody];
}
-(void) initWheels {
SKTexture *_trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
_trainWheelFront = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelFront.zPosition = 1;
_trainWheelFront.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelFront.size.width/2];
_trainWheelFront.physicsBody.allowsRotation = YES;
_trainWheelFront.position = CGPointMake(-80, -82);
[self addChild:_trainWheelFront];
_trainWheelRear = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelRear.zPosition = 1;
_trainWheelRear.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelRear.size.width/2];
_trainWheelRear.physicsBody.allowsRotation = YES;
_trainWheelRear.position = CGPointMake(80, -82);
[self addChild:_trainWheelRear];
//dot used to see if wheels are rotating, no other point
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[_trainWheelFront addChild:dot];
}
//this method is called after the train node is added to the scene in GameScene otherwise will get error adding joints before node is in scene
-(void) createPhysics {
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelFront.physicsBody anchor:_trainWheelFront.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelRear.physicsBody anchor:_trainWheelRear.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
}
-(void) moveLeft {
SKAction *rotateLeft = [SKAction rotateByAngle:6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateLeft];
[_trainWheelRear runAction:rotateLeft];
}
-(void) moveRight {
SKAction *rotateRight = [SKAction rotateByAngle:-6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateRight];
[_trainWheelRear runAction:rotateRight];
}
#end
GameScene.m
#import "GameScene.h"
#import "Train.h"
#interface GameScene()
#property Train *train;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
[self initTrain];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil]; //using notifications and custom view class to handle key presses
}
-(void) initTrain {
_train = [[Train alloc] init];
_train.position = CGPointMake(500, 300);
[self addChild:_train];
[_train createPhysics];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
-(void) keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"keycode = %lu", keyCode);
switch (keyCode) {
case 123:
[self leftArrowPressed];
break;
case 124:
[self rightArrowPressed];
break;
}
}
-(void) leftArrowPressed {
SKAction *moveLeft = [SKAction moveByX:-200 y:0 duration:0.2];
[_train runAction:moveLeft];
[_train moveLeft];
}
-(void) rightArrowPressed {
SKAction *moveRight = [SKAction moveByX:200 y:0 duration:0.2];
[_train runAction:moveRight];
[_train moveRight];
}
#end
Note: This solution causes the entire train to flip and freak out when the left/right keys are pressed. It seems like my pin joints are incorrect, but they seem correct to me :/
Default Initializer of SkSpriteNode is - (instancetype)initWithTexture:(SKTexture *)texture color:(SKColor *)color size:(CGSize)size; Try this,
#interface Train : SKSpriteNode
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size;
-(void) animateWheelsWithTime:(float) time;
#end
#interface Train ()
{
SKSpriteNode *wheel1;
SKSpriteNode *wheel2;
NSMutableArray *wheelsArray;
}
#end
#implementation Train
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size
{
self = [super initWithTexture:nil color:color size:size];
if (self)
{
[self addWheels];
}
return self;
}
-(void)addWheels
{
wheelsArray = [[NSMutableArray alloc]init];
wheel1 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel1 setPosition:CGPointMake(-(self.frame.size.width/2.0f-wheel1.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel1.frame.size.height/2.0f))];
[self addChild:wheel1];
wheel2 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel2 setPosition:CGPointMake((self.frame.size.width/2.0f-wheel2.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel2.frame.size.height/2.0f))];
[self addChild:wheel2];
[wheelsArray addObject:wheel1];
[wheelsArray addObject:wheel2];
}
-(void) animateWheelsWithTime:(float) time
{
for (SKSpriteNode *wheel in wheelsArray)
{
SKAction *act = [SKAction rotateByAngle:3*M_PI duration:time];
[wheel runAction:act];
}
}
#end
In GameScene.m add train like this,
-(void) addTrain
{
Train *train1 = [[Train alloc]initTrainWithColor:[UIColor yellowColor] andSize:CGSizeMake(150, 100)];
[train1 setPosition:CGPointMake(self.size.width/2.0f, self.size.height/2.0f)];
[self addChild:train1];
SKAction *act = [SKAction moveTo:CGPointMake(self.size.width/2.0f, self.size.height - 50) duration:2];
[train1 runAction:act];
[train1 animateWheelsWithTime:2];
}
I tried and it works.
I am new to Mac development, Do we have any methods like
imagev = [NSArray arrayWithObjects
I need some thing like what we do in iOS want to do in mac,
imageVie.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],[UIImage imageNamed:#"2.png"],
[UIImage imageNamed:#"3.png"],[UIImage imageNamed:#"4.png"],
[UIImage imageNamed:#"5.png"],[UIImage imageNamed:#"6.png"],
[UIImage imageNamed:#"7.png"] ,nil];
In iPhone, How can i animate
Regards
I found someone using a Core Animation approach to this issue which was close enough for me. I modified it slightly. You need to #import QuartzCore;
- (void)awakeFromNib
{
CALayer *layer = [CALayer layer];
NSMutableArray *spinnerImages = [NSMutableArray arrayWithCapacity:30u];
for (NSUInteger i = 0; i < 30; ++i)
{
NSString *imageName = [NSString stringWithFormat:#"spinner%#", #(i)];
[spinnerImages addObject:[NSImage imageNamed:imageName]];
}
self.spinnerImages = spinnerImages;
layer.frame = self.imageView.bounds;
[self.imageView setLayer:layer]; // This view is just a container for the layer. Its frame can be managed by a xib.
self.imageView.wantsLayer = YES;
self.spinnerLayer = layer;
}
Then you can animate it like this:
- (void)stopAnimating
{
if ([self.layer.animationKeys containsObject:kAnimationKey])
{
[self.layer removeAnimationForKey:kAnimationKey];
}
}
- (void)startAnimating
{
if ([self.layer.animationKeys containsObject:kAnimationKey])
{
return;
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:kAnimationKey];
[animation setCalculationMode:kCAAnimationDiscrete];
[animation setDuration:1.0f];
[animation setRepeatCount:HUGE_VALF];
[animation setValues:self.spinnerImages];
[self.spinnerLayer addAnimation:animation forKey:kAnimationKey];
}
Building on the great answer by Ben Flynn.
In Swift 3:
// This needs to happen around init.
// NSView (and who inherit from it) does not come with a layer.
// self.layer will be nil at init.
self.layer = CALayer()
self.wantsLayer = true
let layer = CALayer()
let keyPath = "contents" // (I did not find a constant for that key.)
let frameAnimation = CAKeyframeAnimation(keyPath: keyPath)
frameAnimation.calculationMode = kCAAnimationDiscrete
// Duration
// This is the duration of one cycle
let durationOfAnimation: CFTimeInterval = 2.5
frameAnimation.duration = durationOfAnimation
frameAnimation.repeatCount = HUGE// can also use Float.infinity
let imageSeq: [NSImage] = imageSequance // Your images to animate
frameAnimation.values = imageSeq
// Sadly, there are no layout constraints on CALayer.
// If your view will be resized while animating, you'll need to override
// 'func layout()' and calculate aspect ratio if needs be
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)
self.layer?.addSublayer(layer)
If the view is expected to be resized:
Remove these lines:
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect
And call self.needsLayout = true after adding the sublayer. This will cause the layout() to be called.
//let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
//layer.frame = layerRect
//layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)
self.layer?.addSublayer(layer)
self.needsLayout = true
Lastly, override layout():
override func layout() {
super.layout()
var layerFrame = CGRect(origin: CGPoint.zero, size: self.frame.size)
self.myLayer.frame = layerFrame
self.myLayer.bounds = // Adjust ratio as needed.
}
Cocoa doesn't have anything like animatedImageWithImages:duration:. Images in Cocoa can vary in space (different resolutions) and color depth, but not time; a single image is always a static image, never animated.
(There might be an exception for animated GIFs, but GIFs can't display more than 255 or 256 colors per frame, and do not support partial transparency. Moreover, I haven't tried creating or displaying GIFs using the NSImage or CGImage machinery.)
What you'll need to do is create not an image, but a movie. Add images to the movie, varying each one's duration to achieve the playback speed you want. Then, display your movie in a movie view, optionally with the controller hidden.
Swift 4.2
I couldn’t get the previous answers get to work until I added a beginTime. Since Swift 3 some constants changed too. So I’ve converted the solution to Swift 4.2. Also, I thought it would be handy to create it as a CALayer extention:
extension CALayer {
static func image(sequence: [NSImage], duration: CFTimeInterval? = nil, frame: CGRect? = nil) -> CALayer {
let layer = CALayer()
if let f = frame { layer.frame = f }
layer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
let keyPath = "contents"
let keyFrameAnimation = CAKeyframeAnimation(keyPath: keyPath)
keyFrameAnimation.values = sequence
keyFrameAnimation.calculationMode = .discrete
keyFrameAnimation.fillMode = .forwards
keyFrameAnimation.duration = duration ?? CFTimeInterval(sequence.count / 18)
keyFrameAnimation.repeatCount = Float.infinity
keyFrameAnimation.autoreverses = false
keyFrameAnimation.isRemovedOnCompletion = false
keyFrameAnimation.beginTime = 0.0
layer.add(keyFrameAnimation, forKey: keyPath)
return layer
}
}
Use it like
let sequenceLayer = CALayer.image(sequence: imageSequence, duration: 0.55, frame: yourView.bounds)
#interface AnimatedNSImage : NSImage
#property (nonatomic, retain) IBInspectable NSImageView *delegate;
#property (nonatomic, readonly) NSArray *frames;
#property (nonatomic, readonly) CGFloat duration;
- (instancetype) initWithImages:(NSArray*)frames duration:(CGFloat)duration;
#end
And in the .m file...
#interface AnimatedNSImage () {
NSTimer *_scheduledTimer;
NSArray *_frames;
NSUInteger _frameIndex;
CGFloat _duration;
}
#end
#implementation AnimatedNSImage
#synthesize delegate;
- (NSArray *) frames
{
return _frames;
}
- (CGImageRef) CGImage
{
if (_frames && _frames.count >0) {
NSImage *_frame = _frames[_frameIndex];
return _frame.CGImage;
}
return nil;
}
- (NSArray<NSImageRep *> *) representations
{
NSImage *_frame = _frames[_frameIndex];
return _frame.representations;
}
- (CGFloat) duration
{
return _duration;
}
- (void) __showNextFrame:(id)sender
{
_frameIndex = (_frameIndex + 1) % _frames.count;
if (delegate) {
[delegate setNeedsDisplay:YES];
}
}
- (NSSize) size
{
if (_frames && _frames.count >0) {
NSImage *_frame = _frames[_frameIndex];
return _frame.size;
}
return CGSizeZero;
}
- (void) setup
{
_scheduledTimer = [NSTimer scheduledTimerWithTimeInterval:_duration target:self selector:#selector(__showNextFrame:) userInfo:nil repeats:YES];
}
- (void) dealloc
{
[_scheduledTimer invalidate];
_scheduledTimer = nil;
}
- (instancetype) initWithImages:(NSArray *)frames duration:(CGFloat)duration
{
self = [super init];
if (self) {
_frames = frames;
_duration = duration / 100.0f;
[self setup];
}
return self;
}
#end
Note that you will have to assign the delegate (to the NSImageView) in order to invoke a refresh..
An example....
IBOutlet NSImageView *_testGifView;
AnimatedNSImage *_animatedImage = [NSImage animatedImageWithAnimatedGIFURL:[NSURL URLWithString:#"https://media.giphy.com/media/B2zB8mHrHHUXS57Cuz/giphy.gif"]];
_testGifView.image = _animatedImage;
_animatedImage.delegate = _testGifView;
The scheduled timer of course can be adjusted as required as the input time is in centiseconds (as opposed to minutes).
I subclassing TTTableMessageItemCell, got EXC_BAD_ACCESS runtime error. Anythign wrong?
CustomTTTableSubtitleItemCell.h
#import "Three20/Three20.h"
#interface CustomTTTableSubtitleItemCell : TTTableMessageItemCell {
TTButton *_rightButton;
}
#end
CustomTTTableSubtitleItemCell.m
#import "CustomTTTableSubtitleItemCell.h"
#import "CustomTTTableSubtitleItem.h"
#import "XYDefaultStyleSheet.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
static CGFloat kHPadding = 10;
static CGFloat kVPadding = 15;
#interface ButtonStyleSheet : TTDefaultStyleSheet
#end
#implementation ButtonStyleSheet
- (TTStyle*)blueToolbarButton:(UIControlState)state {
TTShape* shape = [TTRoundedRectangleShape shapeWithRadius:4.5];
UIColor* tintColor = RGBCOLOR(30, 110, 255);
return [TTSTYLESHEET toolbarButtonForState:state shape:shape tintColor:tintColor font:nil];
}
#end
#implementation CustomTTTableSubtitleItemCell
+ (CGFloat)tableView:(UITableView*)tableView rowHeightForItem:(id)item {
CustomTTTableSubtitleItem* captionedItem = item;
CGFloat maxWidth = tableView.width - kHPadding*2;
CGSize titleSize = [captionedItem.title sizeWithFont:TTSTYLEVAR(myTitleFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
lineBreakMode:UILineBreakModeWordWrap];
CGSize textSize = [captionedItem.text sizeWithFont:TTSTYLEVAR(myHeadingFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
lineBreakMode:UILineBreakModeWordWrap];
CGSize subtextSize = [captionedItem.caption sizeWithFont:TTSTYLEVAR(mySubtextFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
return kVPadding*2 + titleSize.height + textSize.height + subtextSize.height + kVPadding;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)identifier {
if (self = [super initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:identifier]) {
_item = nil;
[TTStyleSheet setGlobalStyleSheet:[[[ButtonStyleSheet alloc] init] autorelease]];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.detailTextLabel sizeToFit];
self.detailTextLabel.top = kVPadding;
self.textLabel.height = self.detailTextLabel.height;
//_rightButton.frame = CGRectMake(20, self.detailTextLabel.bottom + kVPadding, kImageWidth, kImageHeight);
//_rightButton.alpha = !self.showingDeleteConfirmation;
[_rightButton sizeToFit];
_rightButton.left = self.contentView.width - (_timestampLabel.width + kHPadding);
_rightButton.top = self.height/2;
}
- (id)object {
return _item;
}
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
CustomTTTableSubtitleItem* item = object;
//self.textLabel.textColor = TTSTYLEVAR(myHeadingColor);
// self.textLabel.font = TTSTYLEVAR(myHeadingFont);
// self.textLabel.textAlignment = UITextAlignmentRight;
// self.textLabel.contentMode = UIViewContentModeCenter;
// self.textLabel.lineBreakMode = UILineBreakModeWordWrap;
// self.textLabel.numberOfLines = 0;
//
// self.detailTextLabel.textColor = TTSTYLEVAR(mySubtextColor);
// self.detailTextLabel.font = TTSTYLEVAR(mySubtextFont);
// self.detailTextLabel.textAlignment = UITextAlignmentLeft;
// self.detailTextLabel.contentMode = UIViewContentModeTop;
// self.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
_rightButton = [TTButton
buttonWithStyle:#"blueToolbarButton:" title:item.rightButtonTitle];
}
}
- (void)dealloc {
TT_RELEASE_SAFELY(_rightButton);
[super dealloc];
}
#end
You're creating the TTButton using autorelease, while you're releasing it in your dealloc function. So both the release pool and the dealloc are trying to release your _rightButton TTButton.
in your header file, try adding:
#property (nonatomic, readonly, retain) TTButton* rightButton;
And then create the TTButton using his get function in your source file:
///////////////////////////////////////////////////////////////////////////////////////////////////
- (TTButton*)rightButton {
if (!_rightButton) {
_rightButton = [[TTButton
buttonWithStyle:#"blueToolbarButton:" title:item.rightButtonTitle] retain];
[self.contentView addSubview:rightButton];
}
return rightButton;
}
When using the rightButton, make sure to use self.rightBotton and not _rightButton, such as in the layout function (Because you need to object to be created).
self.rightButton.frame = CGRectMake(20, self.detailTextLabel.bottom + kVPadding, kImageWidth, kImageHeight);
I suggest opening the Three20UI/TTTableMessageItemCell.h & source file and trying to copy the behavior of one of the elements. That's what I did.