iOS

iOS

iOS is the mobile operating system running on the Apple iPhone, iPod touch, and iPad.
Jamel  O'Reilly

Jamel O'Reilly

1660499520

EZAudio: A Simple, Intuitive Audio Framework for iOS and OSX

A simple, intuitive audio framework for iOS and OSX.

Deprecated

EZAudio has recently been deprecated in favor of AudioKit. However, since some people are still forking and using EZAudio I've decided to restore the README as it was. Check out the note below.

Apps Using EZAudio

I'd really like to start creating a list of projects made using EZAudio. If you've used EZAudio to make something cool, whether it's an app or open source visualization or whatever, please email me at syedhali07[at]gmail.com and I'll add it to our wall of fame! To start it off:

  • Detour - Gorgeous location-aware audio walks
  • Jumpshare - Incredibly fast, real-time file sharing
  • Piano Tuner (Home Edition)
  • Piano Tuner (Pro Edition)
  • Music Pitch Detector
  • Piano Prober
  • Multi Tuner

Features

Awesome Components

I've designed six audio components and two interface components to allow you to immediately get your hands dirty recording, playing, and visualizing audio data. These components simply plug into each other and build on top of the high-performance, low-latency AudioUnits API and give you an easy to use API written in Objective-C instead of pure C.

EZAudioDevice

A useful class for getting all the current and available inputs/output on any Apple device. The EZMicrophone and EZOutput use this to direct sound in/out from different hardware components.

EZMicrophone

A microphone class that provides its delegate audio data from the default device microphone with one line of code.

EZOutput

An output class that will playback any audio it is provided by its datasource.

EZAudioFile

An audio file class that reads/seeks through audio files and provides useful delegate callbacks.

EZAudioPlayer

A replacement for AVAudioPlayer that combines an EZAudioFile and a EZOutput to perform robust playback of any file on any piece of hardware.

EZRecorder

A recorder class that provides a quick and easy way to write audio files from any datasource.

EZAudioPlot

A Core Graphics-based audio waveform plot capable of visualizing any float array as a buffer or rolling plot.

EZAudioPlotGL

An OpenGL-based, GPU-accelerated audio waveform plot capable of visualizing any float array as a buffer or rolling plot.

Cross Platform

EZAudio was designed to work transparently across all iOS and OSX devices. This means one universal API whether you're building for Mac or iOS. For instance, under the hood an EZAudioPlot knows that it will subclass a UIView for iOS or an NSView for OSX and the EZMicrophone knows to build on top of the RemoteIO AudioUnit for iOS, but defaults to the system defaults for input and output for OSX.

Examples & Docs

Within this repo you'll find the examples for iOS and OSX to get you up to speed using each component and plugging them into each other. With just a few lines of code you'll be recording from the microphone, generating audio waveforms, and playing audio files like a boss. See the full Getting Started guide for an interactive look into each of components.

Example Projects

EZAudioCoreGraphicsWaveformExample

CoreGraphicsWaveformExampleGif

Shows how to use the EZMicrophone and EZAudioPlot to visualize the audio data from the microphone in real-time. The waveform can be displayed as a buffer or a rolling waveform plot (traditional waveform look).

EZAudioOpenGLWaveformExample

OpenGLWaveformExampleGif

Shows how to use the EZMicrophone and EZAudioPlotGL to visualize the audio data from the microphone in real-time. The drawing is using OpenGL so the performance much better for plots needing a lot of points.

EZAudioPlayFileExample

PlayFileExample

Shows how to use the EZAudioPlayer and EZAudioPlotGL to playback, pause, and seek through an audio file while displaying its waveform as a buffer or a rolling waveform plot.

EZAudioRecordWaveformExample

RecordWaveformExample

Shows how to use the EZMicrophone, EZRecorder, and EZAudioPlotGL to record the audio from the microphone input to a file while displaying the audio waveform of the incoming data. You can then playback the newly recorded audio file using AVFoundation and keep adding more audio data to the tail of the file.

EZAudioWaveformFromFileExample

WaveformExample

Shows how to use the EZAudioFile and EZAudioPlot to animate in an audio waveform for an entire audio file.

EZAudioPassThroughExample

PassthroughExample

Shows how to use the EZMicrophone, EZOutput, and the EZAudioPlotGL to pass the microphone input to the output for playback while displaying the audio waveform (as a buffer or rolling plot) in real-time.

EZAudioFFTExample

FFTExample

Shows how to calculate the real-time FFT of the audio data coming from the EZMicrophone and the Accelerate framework. The audio data is plotted using two EZAudioPlots for the time and frequency displays.

Documentation

The official documentation for EZAudio can be found here: http://cocoadocs.org/docsets/EZAudio/1.1.4/ 
You can also generate the docset yourself using appledocs by running the appledocs on the EZAudio source folder.

Getting Started

To begin using EZAudio you must first make sure you have the proper build requirements and frameworks. Below you'll find explanations of each component and code snippets to show how to use each to perform common tasks like getting microphone data, updating audio waveform plots, reading/seeking through audio files, and performing playback.

Build Requirements

iOS

  • 6.0+

OSX

  • 10.8+

Frameworks

iOS

  • Accelerate
  • AudioToolbox
  • AVFoundation
  • GLKit

OSX

  • Accelerate
  • AudioToolbox
  • AudioUnit
  • CoreAudio
  • QuartzCore
  • OpenGL
  • GLKit

Adding To Project

You can add EZAudio to your project in a few ways: 

1.) The easiest way to use EZAudio is via <a href="http://cocoapods.org/", target="_blank">Cocoapods. Simply add EZAudio to your <a href="http://guides.cocoapods.org/using/the-podfile.html", target="_blank">Podfile like so:

pod 'EZAudio', '~> 1.1.4'

Using EZAudio & The Amazing Audio Engine

If you're also using the Amazing Audio Engine then use the EZAudio/Core subspec like so:

pod 'EZAudio/Core', '~> 1.1.4'

2.) EZAudio now supports Carthage (thanks Andrew and Tommaso!). You can refer to Carthage's installation for a how-to guide: https://github.com/Carthage/Carthage

3.) Alternatively, you can check out the iOS/Mac examples for how to setup a project using the EZAudio project as an embedded project and utilizing the frameworks. Be sure to set your header search path to the folder containing the EZAudio source.

Core Components

EZAudio currently offers six audio components that encompass a wide range of functionality. In addition to the functional aspects of these components such as pulling audio data, reading/writing from files, and performing playback they also take special care to hook into the interface components to allow developers to display visual feedback (see the Interface Components below).

EZAudioDevice

Provides a simple interface for obtaining the current and all available inputs and output for any Apple device. For instance, the iPhone 6 has three microphones available for input, while on OSX you can choose the Built-In Microphone or any available HAL device on your system. Similarly, for iOS you can choose from a pair of headphones connected or speaker, while on OSX you can choose from the Built-In Output, any available HAL device, or Airplay.

EZAudioDeviceInputsExample

Getting Input Devices

To get all the available input devices use the inputDevices class method:

NSArray *inputDevices = [EZAudioDevice inputDevices];

or to just get the currently selected input device use the currentInputDevice method:

// On iOS this will default to the headset device or bottom microphone, while on OSX this will
// be your selected inpupt device from the Sound preferences
EZAudioDevice *currentInputDevice = [EZAudioDevice currentInputDevice];

Getting Output Devices

Similarly, to get all the available output devices use the outputDevices class method:

NSArray *outputDevices = [EZAudioDevice outputDevices];

or to just get the currently selected output device use the currentInputDevice method:

// On iOS this will default to the headset speaker, while on OSX this will be your selected
// output device from the Sound preferences
EZAudioDevice *currentOutputDevice = [EZAudioDevice currentOutputDevice];

EZMicrophone

Provides access to the default device microphone in one line of code and provides delegate callbacks to receive the audio data as an AudioBufferList and float arrays.

Relevant Example Projects

  • EZAudioCoreGraphicsWaveformExample (iOS)
  • EZAudioCoreGraphicsWaveformExample (OSX)
  • EZAudioOpenGLWaveformExample (iOS)
  • EZAudioOpenGLWaveformExample (OSX)
  • EZAudioRecordExample (iOS)
  • EZAudioRecordExample (OSX)
  • EZAudioPassThroughExample (iOS)
  • EZAudioPassThroughExample (OSX)
  • EZAudioFFTExample (iOS)
  • EZAudioFFTExample (OSX)

Creating A Microphone

Create an EZMicrophone instance by declaring a property and initializing it like so:

// Declare the EZMicrophone as a strong property
@property (nonatomic, strong) EZMicrophone *microphone;

...

// Initialize the microphone instance and assign it a delegate to receive the audio data
// callbacks
self.microphone = [EZMicrophone microphoneWithDelegate:self];

Alternatively, you could also use the shared EZMicrophone instance and just assign its EZMicrophoneDelegate.

// Assign a delegate to the shared instance of the microphone to receive the audio data
// callbacks
[EZMicrophone sharedMicrophone].delegate = self;

Setting The Device

The EZMicrophone uses an EZAudioDevice instance to select what specific hardware destination it will use to pull audio data. You'd use this if you wanted to change the input device like in the EZAudioCoreGraphicsWaveformExample for iOS or OSX. At any time you can change which input device is used by setting the device property:

NSArray *inputs = [EZAudioDevice inputDevices];
[self.microphone setDevice:[inputs lastObject]];

Anytime the EZMicrophone changes its device it will trigger the EZMicrophoneDelegate event:


- (void)microphone:(EZMicrophone *)microphone changedDevice:(EZAudioDevice *)device
{
    // This is not always guaranteed to occur on the main thread so make sure you
    // wrap it in a GCD block
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI here
        NSLog(@"Changed input device: %@", device);
    });
}

Note: For iOS this can happen automatically if the AVAudioSession changes the current device.

Getting Microphone Data

To tell the microphone to start fetching audio use the startFetchingAudio function.

// Starts fetching audio from the default device microphone and sends data to EZMicrophoneDelegate
[self.microphone startFetchingAudio];

Once the EZMicrophone has started it will send the EZMicrophoneDelegate the audio back in a few ways. An array of float arrays:

/**
 The microphone data represented as non-interleaved float arrays useful for:
    - Creating real-time waveforms using EZAudioPlot or EZAudioPlotGL
    - Creating any number of custom visualizations that utilize audio!
 */
-(void)   microphone:(EZMicrophone *)microphone
    hasAudioReceived:(float **)buffer
      withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
    __weak typeof (self) weakSelf = self;
    // Getting audio data as an array of float buffer arrays that can be fed into the
    // EZAudioPlot, EZAudioPlotGL, or whatever visualization you would like to do with
    // the microphone data.
    dispatch_async(dispatch_get_main_queue(),^{
        // Visualize this data brah, buffer[0] = left channel, buffer[1] = right channel
        [weakSelf.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize];
    });
}

or the AudioBufferList representation:

/**
 The microphone data represented as CoreAudio's AudioBufferList useful for:
    - Appending data to an audio file via the EZRecorder
    - Playback via the EZOutput

 */
-(void)    microphone:(EZMicrophone *)microphone
        hasBufferList:(AudioBufferList *)bufferList
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    // Getting audio data as an AudioBufferList that can be directly fed into the EZRecorder
    // or EZOutput. Say whattt...
}

Pausing/Resuming The Microphone

Pause or resume fetching audio at any time like so:

// Stop fetching audio
[self.microphone stopFetchingAudio];

// Resume fetching audio
[self.microphone startFetchingAudio];

Alternatively, you could also toggle the microphoneOn property (safe to use with Cocoa Bindings)

// Stop fetching audio
self.microphone.microphoneOn = NO;

// Start fetching audio
self.microphone.microphoneOn = YES;

EZOutput

Provides flexible playback to the default output device by asking the EZOutputDataSource for audio data to play. Doesn't care where the buffers come from (microphone, audio file, streaming audio, etc). As of 1.0.0 the EZOutputDataSource has been simplified to have only one method to provide audio data to your EZOutput instance.

// The EZOutputDataSource should fill out the audioBufferList with the given frame count.
// The timestamp is provided for sample accurate calculation, but for basic use cases can
// be ignored.
- (OSStatus)        output:(EZOutput *)output
 shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
        withNumberOfFrames:(UInt32)frames
                 timestamp:(const AudioTimeStamp *)timestamp;

Relevant Example Projects

  • EZAudioPlayFileExample (iOS)
  • EZAudioPlayFileExample (OSX)
  • EZAudioPassThroughExample (iOS)
  • EZAudioPassThroughExample (OSX)

Creating An Output

Create an EZOutput by declaring a property and initializing it like so:

// Declare the EZOutput as a strong property
@property (nonatomic, strong) EZOutput *output;
...

// Initialize the EZOutput instance and assign it a delegate to provide the output audio data
self.output = [EZOutput outputWithDataSource:self];

Alternatively, you could also use the shared output instance and just assign it an EZOutputDataSource if you will only have one EZOutput instance for your application.

// Assign a delegate to the shared instance of the output to provide the output audio data
[EZOutput sharedOutput].delegate = self;

Setting The Device

The EZOutput uses an EZAudioDevice instance to select what specific hardware destination it will output audio to. You'd use this if you wanted to change the output device like in the EZAudioPlayFileExample for OSX. At any time you can change which output device is used by setting the device property:

// By default the EZOutput uses the default output device, but you can change this at any time
EZAudioDevice *currentOutputDevice = [EZAudioDevice currentOutputDevice];
[self.output setDevice:currentOutputDevice];

Anytime the EZOutput changes its device it will trigger the EZOutputDelegate event:

- (void)output:(EZOutput *)output changedDevice:(EZAudioDevice *)device
{
    NSLog(@"Change output device to: %@", device);
}

Playing Audio

Setting The Input Format

When providing audio data the EZOutputDataSource will expect you to fill out the AudioBufferList provided with whatever inputFormat that is set on the EZOutput. By default the input format is a stereo, non-interleaved, float format (see defaultInputFormat for more information). If you're dealing with a different input format (which is typically the case), just set the inputFormat property. For instance:

// Set a mono, float format with a sample rate of 44.1 kHz
AudioStreamBasicDescription monoFloatFormat = [EZAudioUtilities monoFloatFormatWithSampleRate:44100.0f];
[self.output setInputFormat:monoFloatFormat];

Implementing the EZOutputDataSource

An example of implementing the EZOutputDataSource is done internally in the EZAudioPlayer using an EZAudioFile to read audio from an audio file on disk like so:

- (OSStatus)        output:(EZOutput *)output
 shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
        withNumberOfFrames:(UInt32)frames
                 timestamp:(const AudioTimeStamp *)timestamp
{
    if (self.audioFile)
    {
        UInt32 bufferSize; // amount of frames actually read
        BOOL eof; // end of file
        [self.audioFile readFrames:frames
                   audioBufferList:audioBufferList
                        bufferSize:&bufferSize
                               eof:&eof];
        if (eof && [self.delegate respondsToSelector:@selector(audioPlayer:reachedEndOfAudioFile:)])
        {
            [self.delegate audioPlayer:self reachedEndOfAudioFile:self.audioFile];
        }
        if (eof && self.shouldLoop)
        {
            [self seekToFrame:0];
        }
        else if (eof)
        {
            [self pause];
            [self seekToFrame:0];
            [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidReachEndOfFileNotification
                                                                object:self];
        }
    }
    return noErr;
}

I created a sample project that uses the EZOutput to act as a signal generator to play sine, square, triangle, sawtooth, and noise waveforms. Here's a snippet of code to generate a sine tone:

...
double const SAMPLE_RATE = 44100.0;

- (void)awakeFromNib
{
    //
    // Create EZOutput to play audio data with mono format (EZOutput will convert
    // this mono, float "inputFormat" to a clientFormat, i.e. the stereo output format).
    //
    AudioStreamBasicDescription inputFormat = [EZAudioUtilities monoFloatFormatWithSampleRate:SAMPLE_RATE];
    self.output = [EZOutput outputWithDataSource:self inputFormat:inputFormat];
    [self.output setDelegate:self];
    self.frequency = 200.0;
    self.sampleRate = SAMPLE_RATE;
    self.amplitude = 0.80;
}

- (OSStatus)        output:(EZOutput *)output
 shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
        withNumberOfFrames:(UInt32)frames
                 timestamp:(const AudioTimeStamp *)timestamp
{
    Float32 *buffer = (Float32 *)audioBufferList->mBuffers[0].mData;
    size_t bufferByteSize = (size_t)audioBufferList->mBuffers[0].mDataByteSize;
    double theta = self.theta;
    double frequency = self.frequency;
    double thetaIncrement = 2.0 * M_PI * frequency / SAMPLE_RATE;
    if (self.type == GeneratorTypeSine)
    {
        for (UInt32 frame = 0; frame < frames; frame++)
        {
            buffer[frame] = self.amplitude * sin(theta);
            theta += thetaIncrement;
            if (theta > 2.0 * M_PI)
            {
                theta -= 2.0 * M_PI;
            }
        }
        self.theta = theta;
    }
    else if (... other shapes in full source)
}

For the full implementation of the square, triangle, sawtooth, and noise functions here: (https://github.com/syedhali/SineExample/blob/master/SineExample/GeneratorViewController.m#L220-L305)

Once the EZOutput has started it will send the EZOutputDelegate the audio back as float arrays for visualizing. These are converted inside the EZOutput component from whatever input format you may have provided. For instance, if you provide an interleaved, signed integer AudioStreamBasicDescription for the inputFormat property then that will be automatically converted to a stereo, non-interleaved, float format when sent back in the delegate playedAudio:... method below: An array of float arrays:

/**
 The output data represented as non-interleaved float arrays useful for:
    - Creating real-time waveforms using EZAudioPlot or EZAudioPlotGL
    - Creating any number of custom visualizations that utilize audio!
 */
- (void)       output:(EZOutput *)output
          playedAudio:(float **)buffer
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
    // Update plot, buffer[0] = left channel, buffer[1] = right channel
    });
}

Pausing/Resuming The Output

Pause or resume the output component at any time like so:

// Stop fetching audio
[self.output stopPlayback];

// Resume fetching audio
[self.output startPlayback];

Chaining Audio Unit Effects

Internally the EZOutput is using an AUGraph to chain together a converter, mixer, and output audio units. You can hook into this graph by subclassing EZOutput and implementing the method:

// By default this method connects the AUNode representing the input format converter to
// the mixer node. In subclasses you can add effects in the chain between the converter
// and mixer by creating additional AUNodes, adding them to the AUGraph provided below,
// and then connecting them together.
- (OSStatus)connectOutputOfSourceNode:(AUNode)sourceNode
                  sourceNodeOutputBus:(UInt32)sourceNodeOutputBus
                    toDestinationNode:(AUNode)destinationNode
              destinationNodeInputBus:(UInt32)destinationNodeInputBus
                              inGraph:(AUGraph)graph;

This was inspired by the audio processing graph from CocoaLibSpotify (Daniel Kennett of Spotify has an excellent blog post explaining how to add an EQ to the CocoaLibSpotify AUGraph).

Here's an example of how to add a delay audio unit (kAudioUnitSubType_Delay):

// In interface, declare delay node info property
@property (nonatomic, assign) EZAudioNodeInfo *delayNodeInfo;

// In implementation, overwrite the connection method
- (OSStatus)connectOutputOfSourceNode:(AUNode)sourceNode
                  sourceNodeOutputBus:(UInt32)sourceNodeOutputBus
                    toDestinationNode:(AUNode)destinationNode
              destinationNodeInputBus:(UInt32)destinationNodeInputBus
                              inGraph:(AUGraph)graph
{
    self.delayNodeInfo = (EZAudioNodeInfo *)malloc(sizeof(EZAudioNodeInfo));

    // A description for the time/pitch shifter Device
    AudioComponentDescription delayComponentDescription;
    delayComponentDescription.componentType = kAudioUnitType_Effect;
    delayComponentDescription.componentSubType = kAudioUnitSubType_Delay;
    delayComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    delayComponentDescription.componentFlags = 0;
    delayComponentDescription.componentFlagsMask = 0;

    [EZAudioUtilities checkResult:AUGraphAddNode(graph,
                                                 &delayComponentDescription,
                                                 &self.delayNodeInfo->node)
                        operation:"Failed to add node for time shift"];

    // Get the time/pitch shifter Audio Unit from the node
    [EZAudioUtilities checkResult:AUGraphNodeInfo(graph,
                                                  self.delayNodeInfo->node,
                                                  NULL,
                                                  &self.delayNodeInfo->audioUnit)
                        operation:"Failed to get audio unit for delay node"];

    // connect the output of the input source node to the input of the time/pitch shifter node
    [EZAudioUtilities checkResult:AUGraphConnectNodeInput(graph,
                                                          sourceNode,
                                                          sourceNodeOutputBus,
                                                          self.delayNodeInfo->node,
                                                          0)
                        operation:"Failed to connect source node into delay node"];

    // connect the output of the time/pitch shifter node to the input of the destination node, thus completing the chain.
    [EZAudioUtilities checkResult:AUGraphConnectNodeInput(graph,
                                                          self.delayNodeInfo->node,
                                                          0,
                                                          destinationNode,
                                                          destinationNodeInputBus)
                        operation:"Failed to connect delay to destination node"];
    return noErr;
}

// Clean up
- (void)dealloc
{
    free(self.delayNodeInfo);
}

EZAudioFile

Provides simple read/seek operations, pulls waveform amplitude data, and provides the EZAudioFileDelegate to notify of any read/seek action occuring on the EZAudioFile. This can be thought of as the NSImage/UIImage equivalent of the audio world.

Relevant Example Projects

  • EZAudioWaveformFromFileExample (iOS)
  • EZAudioWaveformFromFileExample (OSX)

Opening An Audio File

To open an audio file create a new instance of the EZAudioFile class.

// Declare the EZAudioFile as a strong property
@property (nonatomic, strong) EZAudioFile *audioFile;

...

// Initialize the EZAudioFile instance and assign it a delegate to receive the read/seek callbacks
self.audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"] delegate:self];

Getting Waveform Data

The EZAudioFile allows you to quickly fetch waveform data from an audio file with as much or little detail as you'd like.

__weak typeof (self) weakSelf = self;
// Get a waveform with 1024 points of data. We can adjust the number of points to whatever level
// of detail is needed by the application
[self.audioFile getWaveformDataWithNumberOfPoints:1024
                                  completionBlock:^(float **waveformData,
                                                    int length)
{
     [weakSelf.audioPlot updateBuffer:waveformData[0]
                       withBufferSize:length];
}];

Reading From An Audio File

Reading audio data from a file requires you to create an AudioBufferList to hold the data. The EZAudio utility function, audioBufferList, provides a convenient way to get an allocated AudioBufferList to use. There is also a utility function, freeBufferList:, to use to free (or release) the AudioBufferList when you are done using that audio data.

Note: You have to free the AudioBufferList, even in ARC.

// Allocate an AudioBufferList to hold the audio data (the client format is the non-compressed
// in-app format that is used for reading, it's different than the file format which is usually
// something compressed like an mp3 or m4a)
AudioStreamBasicDescription clientFormat = [self.audioFile clientFormat];
UInt32 numberOfFramesToRead = 512;
UInt32 channels = clientFormat.mChannelsPerFrame;
BOOL isInterleaved = [EZAudioUtilities isInterleaved:clientFormat];
AudioBufferList *bufferList = [EZAudioUtilities audioBufferListWithNumberOfFrames:numberOfFramesToRead
                                                                 numberOfChannels:channels
                                                                      interleaved:isInterleaved];

// Read the frames from the EZAudioFile into the AudioBufferList
UInt32 framesRead;
UInt32 isEndOfFile;
[self.audioFile readFrames:numberOfFramesToRead
           audioBufferList:bufferList
                bufferSize:&framesRead
                       eof:&isEndOfFile]

When a read occurs the EZAudioFileDelegate receives two events.

An event notifying the delegate of the read audio data as float arrays:

-(void)     audioFile:(EZAudioFile *)audioFile
            readAudio:(float **)buffer
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakSelf.audioPlot updateBuffer:buffer[0]
                          withBufferSize:bufferSize];
    });
}

and an event notifying the delegate of the new frame position within the EZAudioFile:

-(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition
{
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI
    });
}

Seeking Through An Audio File

You can seek very easily through an audio file using the EZAudioFile's seekToFrame: method. The EZAudioFile provides a totalFrames method to provide you the total amount of frames in an audio file so you can calculate a proper offset.

// Get the total number of frames for the audio file
SInt64 totalFrames = [self.audioFile totalFrames];

// Seeks halfway through the audio file
[self.audioFile seekToFrame:(totalFrames/2)];

// Alternatively, you can seek using seconds
NSTimeInterval duration = [self.audioFile duration];
[self.audioFile setCurrentTime:duration/2.0];

When a seek occurs the EZAudioFileDelegate receives the seek event:

-(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition
{
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI
    });
}

EZAudioPlayer

Provides a class that combines the EZAudioFile and EZOutput for file playback of all Core Audio supported formats to any hardware device. Because the EZAudioPlayer internally hooks into the EZAudioFileDelegate and EZOutputDelegate, you should implement the EZAudioPlayerDelegate to receive the playedAudio:... and updatedPosition: events. The EZAudioPlayFileExample projects for iOS and OSX shows how to use the EZAudioPlayer to play audio files, visualize the samples with an audio plot, adjust the volume, and change the output device using the EZAudioDevice class. The EZAudioPlayer primarily uses NSNotificationCenter to post notifications because often times you have one audio player and multiple UI elements that need to listen for player events to properly update.

Creating An Audio Player

// Declare the EZAudioFile as a strong property
@property (nonatomic, strong) EZAudioFile *audioFile;

...

// Create an EZAudioPlayer with a delegate that conforms to EZAudioPlayerDelegate
self.player = [EZAudioPlayer audioPlayerWithDelegate:self];

Playing An Audio File

The EZAudioPlayer uses an internal EZAudioFile to provide data to its EZOutput for output via the EZOutputDataSource. You can provide an EZAudioFile by just setting the audioFile property on the EZAudioPlayer will make a copy of the EZAudioFile at that file path url for its own use.

// Set the EZAudioFile for playback by setting the `audioFile` property
EZAudioFile *audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"]];
[self.player setAudioFile:audioFile];

// This, however, will not pause playback if a current file is playing. Instead
// it's encouraged to use `playAudioFile:` instead if you're swapping in a new
// audio file while playback is already running
EZAudioFile *audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"]];
[self.player playAudioFile:audioFile];

As audio is played the EZAudioPlayerDelegate will receive the playedAudio:..., updatedPosition:..., and, if the audio file reaches the end of the file, the reachedEndOfAudioFile: events. A typical implementation of the EZAudioPlayerDelegate would be something like:

- (void)  audioPlayer:(EZAudioPlayer *)audioPlayer
          playedAudio:(float **)buffer
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
          inAudioFile:(EZAudioFile *)audioFile
{
    __weak typeof (self) weakSelf = self;
    // Update an EZAudioPlot or EZAudioPlotGL to reflect the audio data coming out
    // of the EZAudioPlayer (post volume and pan)
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakSelf.audioPlot updateBuffer:buffer[0]
                          withBufferSize:bufferSize];
    });
}

//------------------------------------------------------------------------------

- (void)audioPlayer:(EZAudioPlayer *)audioPlayer
    updatedPosition:(SInt64)framePosition
        inAudioFile:(EZAudioFile *)audioFile
{
    __weak typeof (self) weakSelf = self;
    // Update any UI controls including sliders and labels
    // display current time/duration
    dispatch_async(dispatch_get_main_queue(), ^{
        if (!weakSelf.positionSlider.highlighted)
        {
            weakSelf.positionSlider.floatValue = (float)framePosition;
            weakSelf.positionLabel.integerValue = framePosition;
        }
    });
}

Seeking

You can seek through the audio file in a similar fashion as with the EZAudioFile. That is, using the seekToFrame: or currentTime property.

// Get the total number of frames and seek halfway
SInt64 totalFrames = [self.player totalFrames];
[self.player seekToFrame:(totalFrames/2)];

// Alternatively, you can seek using seconds
NSTimeInterval duration = [self.player duration];
[self.player setCurrentTime:duration/2.0];

Setting Playback Parameters

Because the EZAudioPlayer wraps the EZOutput you can adjust the volume and pan parameters for playback.

// Make it half as loud, 0 = silence, 1 = full volume. Default is 1.
[self.player setVolume:0.5];

// Make it only play on the left, -1 = left, 1 = right. Default is 0.0 (center)
[self.player setPan:-1.0];

Getting Audio File Parameters

The EZAudioPlayer wraps the EZAudioFile and provides a high level interface for pulling values like current time, duration, the frame index, total frames, etc.

NSTimeInterval  currentTime          = [self.player currentTime];
NSTimeInterval  duration             = [self.player duration];
NSString       *formattedCurrentTime = [self.player formattedCurrentTime]; // MM:SS formatted
NSString       *formattedDuration    = [self.player formattedDuration];    // MM:SS formatted
SInt64          frameIndex           = [self.player frameIndex];
SInt64          totalFrames          = [self.player totalFrames];

In addition, the EZOutput properties are also offered at a high level as well:

EZAudioDevice *outputDevice = [self.player device];
BOOL            isPlaying    = [self.player isPlaying];
float          pan          = [self.player pan];
float          volume       = [self.player volume];

Notifications

The EZAudioPlayer provides the following notifications (as of 1.1.2):

/**
 Notification that occurs whenever the EZAudioPlayer changes its `audioFile` property. Check the new value using the EZAudioPlayer's `audioFile` property.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeAudioFileNotification;

/**
 Notification that occurs whenever the EZAudioPlayer changes its `device` property. Check the new value using the EZAudioPlayer's `device` property.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeOutputDeviceNotification;

/**
 Notification that occurs whenever the EZAudioPlayer changes its `output` component's `pan` property. Check the new value using the EZAudioPlayer's `pan` property.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePanNotification;

/**
 Notification that occurs whenever the EZAudioPlayer changes its `output` component's play state. Check the new value using the EZAudioPlayer's `isPlaying` property.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePlayStateNotification;

/**
 Notification that occurs whenever the EZAudioPlayer changes its `output` component's `volume` property. Check the new value using the EZAudioPlayer's `volume` property.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeVolumeNotification;

/**
 Notification that occurs whenever the EZAudioPlayer has reached the end of a file and its `shouldLoop` property has been set to NO.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidReachEndOfFileNotification;

/**
 Notification that occurs whenever the EZAudioPlayer performs a seek via the `seekToFrame` method or `setCurrentTime:` property setter. Check the new `currentTime` or `frameIndex` value using the EZAudioPlayer's `currentTime` or `frameIndex` property, respectively.
 */
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification;

EZRecorder

Provides a way to record any audio source to an audio file. This hooks into the other components quite nicely to do something like plot the audio waveform while recording to give visual feedback as to what is happening. The EZRecorderDelegate provides methods to listen to write events and a final close event on the EZRecorder (explained below).

Relevant Example Projects

  • EZAudioRecordExample (iOS)
  • EZAudioRecordExample (OSX)

Creating A Recorder

To create an EZRecorder you must provide at least 3 things: an NSURL representing the file path of where the audio file should be written to (an existing file will be overwritten), a clientFormat representing the format in which you will be providing the audio data, and either an EZRecorderFileType or an AudioStreamBasicDescription representing the file format of the audio data on disk.

// Provide a file path url to write to, a client format (always linear PCM, this is the format
// coming from another component like the EZMicrophone's audioStreamBasicDescription property),
// and a EZRecorderFileType constant representing either a wav (EZRecorderFileTypeWAV),
// aiff (EZRecorderFileTypeAIFF), or m4a (EZRecorderFileTypeM4A) file format. The advantage of
// this is that the `fileFormat` property will be automatically filled out for you.
+ (instancetype)recorderWithURL:(NSURL *)url
                   clientFormat:(AudioStreamBasicDescription)clientFormat
                       fileType:(EZRecorderFileType)fileType;

// Alternatively, you can provide a file path url to write to, a client format (always linear
// PCM, this is the format coming from another component like the EZMicrophone's
// audioStreamBasicDescription property), a `fileFormat` representing your custom
// AudioStreamBasicDescription, and an AudioFileTypeID that corresponds with your `fileFormat`.
+ (instancetype)recorderWithURL:(NSURL *)url
                   clientFormat:(AudioStreamBasicDescription)clientFormat
                     fileFormat:(AudioStreamBasicDescription)fileFormat
                audioFileTypeID:(AudioFileTypeID)audioFileTypeID;

Start by declaring an instance of the EZRecorder (you will have one of these per audio file written out)

// Declare the EZRecorder as a strong property
@property (nonatomic, strong) EZRecorder *recorder;

and initialize it using one of the two initializers from above. For instance, using the EZRecorderFileType shortcut initializer you could create an instance like so:

// Example using an EZMicrophone and a string called kAudioFilePath representing a file
// path location on your computer to write out a M4A file.
self.recorder = [EZRecorder recorderWithURL:[NSURL fileURLWithPath:@"/path/to/your/file.m4a"]
                               clientFormat:[self.microphone audioStreamBasicDescription]
                                   fileType:EZRecorderFileTypeM4A];

or to configure your own custom file format, say to write out a 8000 Hz, iLBC file:

// Example using an EZMicrophone, a string called kAudioFilePath representing a file
// path location on your computer, and an iLBC file format.
AudioStreamBasicDescription iLBCFormat = [EZAudioUtilities iLBCFormatWithSampleRate:8000];
self.recorder = [EZRecorder recorderWithURL:[NSURL fileURLWithPath:@"/path/to/your/file.caf"]
                               clientFormat:[self.microphone audioStreamBasicDescription]
                                 fileFormat:iLBCFormat
                            audioFileTypeID:kAudioFileCAFType];

Recording Some Audio

Once you've initialized your EZRecorder you can append data by passing in an AudioBufferList and its buffer size like so:

// Append the microphone data coming as a AudioBufferList with the specified buffer size
// to the recorder
-(void)    microphone:(EZMicrophone *)microphone
        hasBufferList:(AudioBufferList *)bufferList
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    // Getting audio data as a buffer list that can be directly fed into the EZRecorder. This is
    // happening on the audio thread - any UI updating needs a GCD main queue block.
    if (self.isRecording)
    {
        // Since we set the recorder's client format to be that of the EZMicrophone instance,
        // the audio data coming in represented by the AudioBufferList can directly be provided
        // to the EZRecorder. The EZRecorder will internally convert the audio data from the
        // `clientFormat` to `fileFormat`.
        [self.recorder appendDataFromBufferList:bufferList
                                 withBufferSize:bufferSize];
    }
}

Responding to an EZRecorder after it has written audio data

Once audio data has been successfully written with the EZRecorder it will notify the EZRecorderDelegate of the event so it can respond via:

// Triggers after the EZRecorder's `appendDataFromBufferList:withBufferSize:` method is called
// so you can update your interface accordingly.
- (void)recorderUpdatedCurrentTime:(EZRecorder *)recorder
{
    __weak typeof (self) weakSelf = self;
    // This will get triggerd on the thread that the write occured on so be sure to wrap your UI
    // updates in a GCD main queue block! However, I highly recommend you first pull the values
    // you'd like to update the interface with before entering the GCD block to avoid trying to
    // fetch a value after the audio file has been closed.
    NSString *formattedCurrentTime = [recorder formattedCurrentTime]; // MM:SS formatted
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update label
        weakSelf.currentTimeLabel.stringValue = formattedCurrentTime;
    });
}

Closing An Audio File

When you're recording is done be sure to call the closeAudioFile method to make sure the audio file written to disk is properly closed before you attempt to read it again.

// Close the EZRecorder's audio file BEFORE reading
[self.recorder closeAudioFile];

This will trigger the EZRecorder's delegate method:

- (void)recorderDidClose:(EZRecorder *)recorder
{
    recorder.delegate = nil;
}

Interface Components

EZAudio currently offers two drop in audio waveform components that help simplify the process of visualizing audio.

EZAudioPlot

Provides an audio waveform plot that uses CoreGraphics to perform the drawing. On iOS this is a subclass of UIView while on OSX this is a subclass of NSView. As of the 1.0.0 release, the waveforms are drawn using CALayers where compositing is done on the GPU. As a result, there have been some huge performance gains and CPU usage per real-time (i.e. 60 frames per second redrawing) plot is now about 2-3% CPU as opposed to the 20-30% we were experiencing before.

Relevant Example Projects

  • EZAudioCoreGraphicsWaveformExample (iOS)
  • EZAudioCoreGraphicsWaveformExample (OSX)
  • EZAudioRecordExample (iOS)
  • EZAudioRecordExample (OSX)
  • EZAudioWaveformFromFileExample (iOS)
  • EZAudioWaveformFromFileExample (OSX)
  • EZAudioFFTExample (iOS)
  • EZAudioFFTExample (OSX)

Creating An Audio Plot

You can create an audio plot in the interface builder by dragging in a UIView on iOS or an NSView on OSX onto your content area. Then change the custom class of the UIView/NSView to EZAudioPlot.

EZAudioPlotInterfaceBuilder

Alternatively, you can could create the audio plot programmatically

// Programmatically create an audio plot
EZAudioPlot *audioPlot = [[EZAudioPlot alloc] initWithFrame:self.view.frame];
[self.view addSubview:audioPlot];

Customizing The Audio Plot

All plots offer the ability to change the background color, waveform color, plot type (buffer or rolling), toggle between filled and stroked, and toggle between mirrored and unmirrored (about the x-axis). For iOS colors are of the type UIColor while on OSX colors are of the type NSColor.

// Background color (use UIColor for iOS)
audioPlot.backgroundColor = [NSColor colorWithCalibratedRed:0.816
                                                      green:0.349
                                                       blue:0.255
                                                      alpha:1];
// Waveform color (use UIColor for iOS)
audioPlot.color = [NSColor colorWithCalibratedRed:1.000
                                            green:1.000
                                             blue:1.000
                                            alpha:1];
// Plot type
audioPlot.plotType = EZPlotTypeBuffer;
// Fill
audioPlot.shouldFill = YES;
// Mirror
audioPlot.shouldMirror = YES;

IBInspectable Attributes

Also, as of iOS 8 you can adjust the background color, color, gain, shouldFill, and shouldMirror parameters directly in the Interface Builder via the IBInspectable attributes:

EZAudioPlotInspectableAttributes

Updating The Audio Plot

All plots have only one update function, updateBuffer:withBufferSize:, which expects a float array and its length.

// The microphone component provides audio data to its delegate as an array of float buffer arrays.
- (void)   microphone:(EZMicrophone *)microphone
     hasAudioReceived:(float **)buffer
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    /**
     Update the audio plot using the float array provided by the microphone:
       buffer[0] = left channel
       buffer[1] = right channel
     Note: Audio updates happen asynchronously so we need to make sure
         sure to update the plot on the main thread
     */
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakSelf.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize];
    });
}

EZAudioPlotGL

Provides an audio waveform plot that uses OpenGL to perform the drawing. The API this class are exactly the same as those for the EZAudioPlot above. On iOS this is a subclass of the GLKView while on OSX this is a subclass of the NSOpenGLView. In most cases this is the plot you want to use, it's GPU-accelerated, can handle lots of points while displaying 60 frames per second (the EZAudioPlot starts to choke on anything greater than 1024), and performs amazingly on all devices. The only downside is that you can only have one OpenGL plot onscreen at a time. However, you can combine OpenGL plots with Core Graphics plots in the view hierachy (see the EZAudioRecordExample for an example of how to do this).

Relevant Example Projects

  • EZAudioOpenGLWaveformExample (iOS)
  • EZAudioOpenGLWaveformExample (OSX)
  • EZAudioPlayFileExample (iOS)
  • EZAudioPlayFileExample (OSX)
  • EZAudioRecordExample (iOS)
  • EZAudioRecordExample (OSX)
  • EZAudioPassThroughExample (iOS)
  • EZAudioPassThroughExample (OSX)

Creating An OpenGL Audio Plot

You can create an audio plot in the interface builder by dragging in a UIView on iOS or an NSView on OSX onto your content area. Then change the custom class of the UIView/NSView to EZAudioPlotGL.

EZAudioPlotGLInterfaceBuilder

Alternatively, you can could create the EZAudioPlotGL programmatically

// Programmatically create an audio plot
EZAudioPlotGL *audioPlotGL = [[EZAudioPlotGL alloc] initWithFrame:self.view.frame];
[self.view addSubview:audioPlotGL];

Customizing The OpenGL Audio Plot

All plots offer the ability to change the background color, waveform color, plot type (buffer or rolling), toggle between filled and stroked, and toggle between mirrored and unmirrored (about the x-axis). For iOS colors are of the type UIColor while on OSX colors are of the type NSColor.

// Background color (use UIColor for iOS)
audioPlotGL.backgroundColor = [NSColor colorWithCalibratedRed:0.816
                                                        green:0.349
                                                         blue:0.255
                                                        alpha:1];
// Waveform color (use UIColor for iOS)
audioPlotGL.color = [NSColor colorWithCalibratedRed:1.000
                                              green:1.000
                                               blue:1.000
                                              alpha:1];
// Plot type
audioPlotGL.plotType = EZPlotTypeBuffer;
// Fill
audioPlotGL.shouldFill = YES;
// Mirror
audioPlotGL.shouldMirror = YES;

IBInspectable Attributes

Also, as of iOS 8 you can adjust the background color, color, gain, shouldFill, and shouldMirror parameters directly in the Interface Builder via the IBInspectable attributes:

EZAudioPlotGLInspectableAttributes

Updating The OpenGL Audio Plot

All plots have only one update function, updateBuffer:withBufferSize:, which expects a float array and its length.

// The microphone component provides audio data to its delegate as an array of float buffer arrays.
- (void)   microphone:(EZMicrophone *)microphone
     hasAudioReceived:(float **)buffer
       withBufferSize:(UInt32)bufferSize
 withNumberOfChannels:(UInt32)numberOfChannels
{
    /**
     Update the audio plot using the float array provided by the microphone:
       buffer[0] = left channel
       buffer[1] = right channel
     Note: Audio updates happen asynchronously so we need to make sure
         sure to update the plot on the main thread
     */
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakSelf.audioPlotGL updateBuffer:buffer[0] withBufferSize:bufferSize];
    });
}

Acknowledgements

The following people rock:

  • My brother, Reza Ali, for walking me through all the gritty details of OpenGL and his constant encouragement through this journey to 1.0.0.
  • Aure Prochazka for his amazing work on AudioKit and his encouragement to bring EZAudio to 1.0.0
  • Daniel Kennett for writing this great blog post that inspired the rewrite of the EZOutput in 1.0.0.
  • Michael Tyson for creating the TPCircularBuffer and all his contributions to the community including the Amazing Audio Engine, Audiobus, and all the tasty pixel blog posts.
  • Chris Adamson and Kevin Avila for writing the amazing Learning Core Audio book.

Deprecated

alt text

As of today, June 13, 2016, I’m officially deprecating EZAudio. I’d like to thank everyone for the support over the last few years I’ve been hacking on EZAudio and working to make it better.

Alternatives

The best alternative to EZAudio is now AudioKit. Note that The Amazing Audio Engine and The Amazing Audio Engine 2 have now both been retired as well. Any further contributions I make to iOS/macOS/tvOS audio programming will be to AudioKit.

Why?

EZAudio started as a pet project of mine in 2013 in an attempt to reduce the amount of duplicate code I was writing for my own iOS/Mac audio projects. I originally just wanted to record audio from an iPhone’s mic and plot a waveform. This over time quickly grew into the collection of classes you may know today (EZMicrophone, EZAudioPlot, etc).

I apologize for not being more active in addressing the issues and pull requests, but I’m hoping you all understand I’m only one person. EZAudio was solely written and maintained by me out of love during weeks of time I wasn’t making any money, all while living in one of the most expensive cities in the world. Like many of you, I spend the majority of my time working full-time to sustain myself (I have bills and rent to pay too!). I’m transitioning to non-audio ventures and will hopefully be able to share another cool open source project soon enough.

Getting the opportunity to work on EZAudio with all of you has been an incredibly insightful experience and I’m incredibly grateful to have gotten a chance to share it with all of you. Thank you.

Is all of it broken?

As I’m writing this I’m a little hesitant to say it’s all broken (despite all the issues filed), but I’d recommend forking this repo from this point forward and using at your own risk. I’ll probably be continuing to use EZAudio’s EZAudioPlot for rendering waveforms in my future projects, however, please don’t expect any updates to this repo. Deprecated means I won’t be responding to issues, EZAudio-related emails, or pushing any new changes to this repo.

Author: syedhali
Source Code: https://github.com/syedhali/EZAudio
License: View license

#ios #objective-c 

EZAudio: A Simple, Intuitive Audio Framework for iOS and OSX
Jamel  O'Reilly

Jamel O'Reilly

1660487700

NumericAnnex: Numeric Facilities For Swift

NumericAnnex

NumericAnnex supplements the numeric facilities provided in the Swift standard library.

Features

  • The exponentiation operator ** and the compound assignment operator **=.
  • Extension methods for BinaryInteger exponentiation, square root, cube root, greatest common divisor, and least common multiple.
  • Math, a protocol for signed numeric types that support elementary functions.
  • Real, a protocol for floating-point types that support elementary functions and a selection of special functions.
  • PRNG, a protocol for pseudo-random number generators.
  • Rational, a value type to represent rational values which supports division by zero.
  • Complex, a value type to represent complex values in Cartesian form.
  • Random and Random.Xoroshiro, two reference types implementing efficient pseudo-random number generators.

Note: This project is in the early stages of development and is not production-ready at this time.

Requirements

NumericAnnex requires Swift 4.1 (swift-4.1-branch) or Swift 4.2 (master). On Apple platforms, it also requires the Security framework for cryptographically secure random bytes.

Installation

After NumericAnnex has been cloned or downloaded locally, build the library using the command swift build (macOS) or swift build -Xcc -D_GNU_SOURCE (Linux). Run tests with the command swift test (macOS) or swift test -Xcc -D_GNU_SOURCE (Linux). An Xcode project can be generated with the command swift package generate-xcodeproj.

To add the package as a dependency using CocoaPods, insert the following line in your Podfile:

pod 'NumericAnnex', '~> 0.1.19'

Swift Package Manager can also be used to add the package as a dependency. See Swift documentation for details.

Basic Usage

import NumericAnnex

print(2 ** 3)
// Prints "8".

print(4.0 ** 5.0)
// Prints "1024.0".

print(Int.cbrt(8))
// Prints "2".

print(Double.cbrt(27.0))
// Prints "3.0".

var x: Ratio = 1 / 4
// Ratio is a type alias for Rational<Int>.

print(x.reciprocal())
// Prints "4".

x *= 8
print(x + x)
// Prints "4".

x = Ratio(Float.phi) // Golden ratio.
print(x)
// Prints "13573053/8388608".

var z: Complex64 = 42 * .i
// Complex64 is a type alias for Complex<Float>.

print(Complex.sqrt(z))
// Prints "4.58258 + 4.58258i".

z = .pi + .i * .log(2 - .sqrt(3))
print(Complex.cos(z).real)
// Prints "-2.0".

Documentation

All public protocols, types, and functions have been carefully documented in the code. See the formatted reference for details.

The project adheres to many design patterns found in the Swift standard library. For example, Math types provide methods such as cubeRoot() and tangent() just as FloatingPoint types provide methods such as squareRoot().

No free functions are declared in this library unless they overload existing ones in the Swift standard library. Instead, functions such as cbrt(_:) and tan(_:) are provided as static members. This avoids collisions with C standard library functions that you may wish to use. It also promotes clarity at the call site when the result of a complex operation differs from that of its real counterpart (e.g., Complex128.cbrt(-8) != -2).

Future Directions

  • Add more tests, including performance tests
  • Design and implement additional methods on PRNG

License

Portions of the complex square root and elementary transcendental functions use checks for special values adapted from libc++. Code in libc++ is dual-licensed under the MIT and UIUC/NCSA licenses.

Author: xwu
Source Code: https://github.com/xwu/NumericAnnex
License: MIT license

#ios #swift 

NumericAnnex: Numeric Facilities For Swift
Jamel  O'Reilly

Jamel O'Reilly

1660476840

Metron: Geometry, Simplified, Completely Written in Swift

Metron

Geometry, simplified.

Metron is a comprehensive collection of geometric functions and types that extend the 2D geometric primitives provided by CoreGraphics. Completely written in Swift, Metron allows you to express complex geometric calculations in very intuitive statements:

Example 1.

Circle(in: viewFrame).contains(touchPoint)

↳ creates a Circle that fits (centered) inside viewFrame, and checks if the touchPoint is inside that circle.

Example 2.

(Angle(.pi) + Angle(270, unit: .degrees)).normalized // Angle of 0.5pi (90°)

↳ adds two Angles, one expressed in radians (default) and one in degrees. The sum is normalized to within 0 and 2𝛑 radians (or 0 and 360 degrees).

Example 3.

LineSegment(a: startPoint, b: currentPoint).intersection(with: viewFrame.lineSegment(for: minYEdge))

↳ creates a LineSegment between a (touch) start point and current point, and returns the intersection with the minYEdge of a view's frame (if these line segments indeed intersect).

Example 4.

let rotatedPoints = viewFrame.points.map { $0.applying(rotationTransform, anchorPoint: rotationPoint) }
let path: CGPath = Polygon(points: rotatedPoints).path

↳ maps each corner point of a view's frame (a CGRect) to a point to which a rotationTransform is applied, taking rotationPoint as the anchor point for the transform. With these points, a Polygon is initialized, representing the rotated rectangular frame of the view. From that polygon, we derive a CGPath that can then be drawn.

And there's much more...

Extensions

For CGPoint

  •  Distance to other CGPoint
  •  Clipping to CGRect
  •  Relative position in CGRect
  •  Normalized position in CGRect
  •  Convert to CGVector
  •  Round to non-decimal components
  •  Addition, subtraction, multiplication...
  •  Convex hull for an array of CGPoints (returns a Polygon)

For CGVector

  •  Magnitude
  •  Angle
  •  Convenience initializer with magnitude and angle
  •  Convert to CGPoint
  •  Inverse
  •  Dominant edge
  •  Dominant corner
  •  Derive line through point
  •  Derive line segment from point
  •  CGAffineTransform extensions
  •  Addition, subtraction, multiplication...

For CGSize

  •  Area
  •  Swap width and height
  •  Clip to other CGSize
  •  Scaling using multiplication and division

For CGRect

  •  Many convenience initializers, including AspectFit / AspectFill for size
  •  Scaling
  •  Corner points
  •  Edges as line segments
  •  Area
  •  Center
  •  Perimeter
  •  CGPath

For CGRectEdge

  •  Axis (x or y)
  •  Adjacent corners
  •  Opposite edge

For CGAffineTransform

  •  Create translation transform using CGVector
  •  Apply transform with a specified anchor point

New Types

Line

  •  Slope
  •  Y-intercept
  •  X-intercept
  •  Horizontal? / vertical? / parallel(to: ...)?
  •  Get a perpendicular line
  •  Determine intersection with other Line or LineSegment

LineSegment

  •  Length
  •  Derive a Line
  •  Rotate
  •  Determine intersection with other Line or LineSegment
  •  CGPath

Circle

  •  Radius
  •  Diameter
  •  Circumference
  •  Area
  •  Center
  •  Width / Height
  •  Bounding rect
  •  Contains point?
  •  CGPath
  •  Points along the perimeter (divide the circle into steps, rotating in a specific direction...)

Triangle

  •  Vertices (as CGPoint)
  •  Sides (as LineSegment)
  •  Angles (as Angle, see further on)
  •  Angle bisectors (as LineSegment)
  •  Altitudes (as LineSegment)
  •  Equilateral? / isosceles? / scalene? / right? / oblique? / acute? / obtuse?
  •  Centroid
  •  Circumcenter
  •  Incenter
  •  Orthocenter
  •  Area
  •  Bounding rect
  •  Contains point?
  •  CGPath

Square

  •  Edge length
  •  Area
  •  Perimeter
  •  Center
  •  CGPath
  •  CGRect

Polygon

  •  Init with points or with line segments
  •  Edge count
  •  Self-intersecting?
  •  Convex? / concave?
  •  Area
  •  Perimeter
  •  Center
  •  Bounding rect
  •  Contains point?
  •  CGPath

Corner

  •  Adjacent edges (CGRectEdges)
  •  Opposite corner

Angle

  •  Init with radians or degrees
  •  Convert unit
  •  Normalize
  •  Invert
  •  Compare
  •  Addition, subtraction, multiplication...
  •  Basic trigonometric functions also accept Angle
  •  Create rotation CGAffineTransform with Angle

Installation

CocoaPods

Metron is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "Metron"

Carthage

Metron is also available through Carthage. To install it, simply add the following line to your Cartfile:

github "toineheuvelmans/Metron"

Swift Package Manager

Metron can also be used with the Swift Package Manager. Add Metron to the dependencies value of your Package.swift:

dependencies: [
    .Package(url: "https://github.com/toineheuvelmans/metron.git", majorVersion: 1)
]

Author: toineheuvelmans
Source Code: https://github.com/toineheuvelmans/Metron
License: MIT license

#ios #swift 

Metron: Geometry, Simplified, Completely Written in Swift
Jamel  O'Reilly

Jamel O'Reilly

1660458540

Expression: A Swift Framework for Evaluating Expressions At Runtime

Introduction

What?

Expression is a Swift framework for evaluating expressions at runtime on Apple and Linux platforms

The Expression library is split into two parts:

The Expression class, which is similar to Foundation's built-in NSExpression class, but with better support for custom operators, a more Swift-friendly API, and superior performance.

AnyExpression, an extension of Expression that handles arbitrary types and provides additional built-in support for common types such as String, Dictionary, Array and Optional.

Why?

There are many situations where it is useful to be able to evaluate a simple expression at runtime. Some are demonstrated in the example apps included with the library:

  • A scientific calculator
  • A CSS-style color string parser
  • A basic layout engine, similar to AutoLayout

But there are other possible applications, e.g.

  • A spreadsheet app
  • Configuration (e.g. using expressions in a config file to avoid data duplication)
  • The basis for simple scripting language

(If you find any other use cases, let me know and I'll add them)

Normally these kind of calculations would involve embedding a heavyweight interpreted language such as JavaScript or Lua into your app. Expression avoids that overhead, and is also more secure as it reduces the risk of arbitrary code injection or crashes due to infinite loops, buffer overflows, etc.

Expression is fast, lightweight, well-tested, and written entirely in Swift. It is substantially faster than using JavaScriptCore for evaluating simple expressions (see the Benchmark app for a scientific comparison.

How?

Expression works by parsing an expression string into a tree of symbols, which can then be evaluated at runtime. Each symbol maps to a Swift closure (function) which is executed during evaluation. There are built-in functions representing common math operations, or you can provide your own custom ones.

Although the Expression class only works with Double values, AnyExpression uses a technique called NaN boxing to reference arbitrary data via the unused bit patterns in the IEEE floating point specification.

Usage

Installation

The Expression class is encapsulated in a single file, and everything public is prefixed or name-spaced, so you can simply drag the Expression.swift file into your project to use it. If you wish to use the AnyExpression extension then include the AnyExpression.swift file as well.

If you prefer, there's a framework that you can import which includes both the Expression and AnyExpression classes. You can install this manually by drag and drop, or automatically using CocoaPods, Carthage, or Swift Package Manager.

To install Expression using CocoaPods, add the following to your Podfile:

pod 'Expression', '~> 0.13'

To install using Carthage, add this to your Cartfile:

github "nicklockwood/Expression" ~> 0.13

To install using Swift Package Manager, add this to the dependencies: section in your Package.swift file:

.package(url: "https://github.com/nicklockwood/Expression.git", .upToNextMinor(from: "0.13.0")),

Integration

You create an Expression instance by passing a string containing your expression, and (optionally) any or all of the following:

  • A set of configuration options - used to enabled or disable certain features
  • A dictionary of named constants - this is the simplest way to specify predefined constants
  • A dictionary of named array constants - this is the simplest way to specify predefined arrays of related values
  • A dictionary of symbols and SymbolEvaluator functions - this allows you to provide custom variables, functions or operators

You can then calculate the result by calling the evaluate() method.

Note: The evaluate() function for a given Expression or AnyExpression instance is thread-safe, meaning that you can call it concurrently from multiple threads.

By default, Expression already implements most standard math functions and operators, so you only need to provide a custom symbol dictionary if your app needs to support additional functions or variables. You can mix and match implementations, so if you have some custom constants or arrays and some custom functions or operators, you can provide separate constants and symbols dictionaries.

Here are some examples:

// Basic usage:
// Only using built-in math functions

let expression = Expression("5 + 6")
let result = try expression.evaluate() // 11

// Intermediate usage:
// Custom constants, variables and  and functions

var bar = 7 // variable
let expression = Expression("foo + bar + baz(5) + rnd()", constants: [
    "foo": 5,
], symbols: [
    .variable("bar"): { _ in bar },
    .function("baz", arity: 1): { args in args[0] + 1 },
    .function("rnd", arity: 0): { _ in arc4random() },
])
let result = try expression.evaluate()

// Advanced usage:
// Using the alternative constructor to dynamically hex color literals

let hexColor = "#FF0000FF" // rrggbbaa
let expression = Expression(hexColor, pureSymbols: { symbol in
    if case .variable(let name) = symbol, name.hasPrefix("#") { {
        let hex = String(name.characters.dropFirst())
        guard let value = Double("0x" + hex) else {
            return { _ in throw Expression.Error.message("Malformed color constant #\(hex)") }
        }
        return { _ in value }
    }
    return nil // pass to default evaluator
})
let color: UIColor = {
    let rgba = UInt32(try expression.evaluate())
    let red = CGFloat((rgba & 0xFF000000) >> 24) / 255
    let green = CGFloat((rgba & 0x00FF0000) >> 16) / 255
    let blue = CGFloat((rgba & 0x0000FF00) >> 8) / 255
    let alpha = CGFloat((rgba & 0x000000FF) >> 0) / 255
    return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}()

Note that the evaluate() function may throw an error. An error will be thrown automatically during evaluation if the expression was malformed, or if it references an unknown symbol. Your custom symbol implementations may also throw application-specific errors, as in the colors example above.

For a simple, hard-coded expression like the first example there is no possibility of an error being thrown, but if you accept user-entered expressions, you must always ensure that you catch and handle errors. The error messages produced by Expression are detailed and human-readable (but not localized).

do {
    let result = try expression.evaluate()
    print("Result: \(result)")
} catch {
    print("Error: \(error)")
}

When using the constants, arrays and symbols dictionaries, error message generation is handled automatically by the Expression library. If you need to support dynamic symbol decoding (as in the hex color example earlier), you can use the init(impureSymbols:pureSymbols) initializer, which is a little bit more complex.

The init(impureSymbols:pureSymbols) initializer accepts a pair of lookup functions that take a Symbol and return a SymbolEvaluator function. This interface is very powerful because it allows you to dynamically resolve symbols (such as the hex color constants in the colors example) without needing to create a dictionary of all possible values in advance.

For each symbol, your lookup functions can return either a SymbolEvaluator function, or nil. If you do not recognize a symbol, you should return nil so that it can be handled by the default evaluator. If neither lookup function matches the symbol, and it is not one of the standard math or boolean functions, evaluate() will throw an error.

In some cases you may recognize a symbol, but be certain that it is incorrect, and this is an opportunity to provide a more specific error message than Expression would generate by default. The following example matches a function bar with an arity of 1 (meaning that it takes one argument). This will only match calls to bar that take a single argument, and will ignore calls with zero or multiple arguments.

switch symbol {
case .function("bar", arity: 1):
    return { args in args[0] + 1 }
default:
    return nil // pass to default evaluator
}

Since bar is a custom function, we know that it should only take one argument, so it's more helpful to throw an error if it is called with the wrong number of arguments, rather than returning nil to indicate that the function doesn't exist. That would look something like this:

switch symbol {
case .function("bar", let arity):
    guard arity == 1 else {
        return { _ in throw Expression.Error.message("function bar expects 1 argument") }
    }
    return { arg in args[0] + 1 }
default:
    return nil // pass to default evaluator
}

Note: Newer versions of Expression can correctly report trivial arity errors like this anyway, so this is a slightly contrived example, but this approach may be useful for other types of error, such as when arguments are out of range, or the wrong type.

Symbols

Expressions are formed from a mixture of numeric literals and symbols, which are instances of the Expression.Symbol enum type. The built-in math and boolean libraries define a number of standard symbols, but you are free to define your own.

The Expression.Symbol enum supports the following symbol types:

Variables

.variable(String)

This is an alphanumeric identifier representing a constant or variable in an expression. Identifiers can be any sequence of letters and numbers, beginning with a letter, underscore (_), dollar symbol ($), at sign (@) or hash/pound sign (#).

Like Swift, Expression allows unicode characters in identifiers, such as emoji and scientific symbols. Unlike Swift, Expression's identifiers may also contain periods (.) as separators, which is useful for name-spacing (as demonstrated in the Layout example app).

The parser also accepts quoted strings as identifiers. Single quotes (') , double quotes (") , or backticks () may be used. Since Expression` only deals with numeric values, it's up to your application to map these string identifiers to numbers (if you are using AnyExpression then this is handled automatically).

Unlike regular identifiers, quoted identifiers can contain any unicode character, including spaces. Newlines, quotes and other special characters can be escaped using a backslash (). Escape sequences are decoded for you, but the outer quotes are retained so you can distinguish strings from other identifiers.

Finally, unquoted identifiers are permitted to end with a single quote ('), as this is a common notation used in mathematics to indicate modified values. A quote at any other point in the identifier will be treated as the end of the name.

To verify that a given string is safe for use as an identifier, you can use the Expression.isValidIdentifier() method.

Operators

.infix(String)
.prefix(String)
.postfix(String)

These symbols represent operators. Operators can be one or more characters long, and can contain almost any symbol that doesn't conflict with a valid identifier name, with some caveats:

Comma (,) is a valid operator on its own, but cannot form part of a longer character sequence

The bracket characters [, '(', '{', and their counterparts are reserved and cannot be used as operators

An operator may begin with one or more dots (.) or hyphens (-), but a dot or hyphen cannot appear after any other character. The following are permitted:

..., ..<, ., -, --, -=, -->

but the following are not:

+., >.<, *-, -+-, <--, .-, -.

To verify that a given character sequence is safe for use as an operator, you can use the Expression.isValidOperator() method.

You can overload existing infix operators with a post/prefix variant, or vice-versa. Disambiguation depends on the white-space surrounding the operator (which is the same approach used by Swift).

Any valid identifier may also be used as an infix operator, by placing it between two operands, or as a postfix operator, by placing it after an operand. For example, you could define m and cm as postfix operators when handling distance logic, or use and as a more readable alternative to the boolean && operator.

Operator precedence follows standard BODMAS order, with multiplication/division given precedence over addition/subtraction. Prefix operators take precedence over postfix operators, which take precedence over infix ones. There is currently no way to specify precedence for custom operators - they all have equal priority to addition/subtraction.

Standard boolean operators are supported, and follow the normal precedence rules, with the caveat that short-circuiting (where the right-hand argument(s) may not be evaluated, depending on the left-hand-side) is not supported. The parser will also recognize the ternary ?: operator, treating a ? b : c as a single infix operator with three arguments.

Functions

.function(String, arity: Arity)

A function symbol is defined with a name and an Arity, which is the number of arguments that it expects. The Arity type is an enum that can be set to either exactly(Int) or atLeast(Int) for variadic functions. A given function name can be overloaded multiple times with different arities.

Note: Arity conforms to ExpressibleByIntegerLiteral, so for fixed-arity functions you can just write .function("foo", arity: 2) instead of .function("foo", arity: .exactly(2))

Functions are called by using their name followed by a comma-delimited sequence of arguments in parentheses. If the argument count does not match any of the specified arity variants, an arityError will be thrown.

Since function symbols must have a name, it is not directly possible to use anonymous functions in an expression (e.g. functions that are stored in a variable, or returned by another function).

There is syntax support for this however, if you implement the function call operator .infix("()"), which accepts one or more arguments, with the first being treated as the function to be called. This is of limited use in Expression (where values are all numeric) but AnyExpression uses this approach to provide full support for [anonymous functions(#anonymous-functions).

Arrays

.array(String)

Array symbols represent a sequence of values that can be accessed by index. Array symbols are referenced in an expression by using their name followed by an index argument in square brackets.

The simplest way to use arrays with Expression is to pass in a constant array value via the arrays initializer argument. For variable arrays, you can return an .array() symbol implementation via the symbols argument.

Expression also supports Swift-style array literal syntax like [1, 2, 3] and subscripting of arbitrary expressions like (a + b)[c]. Array literals map to the array literal constructor symbol .function("[]", arity: .any) and subscripting maps to the array subscripting operator .infix("[]").

Because Expression cannot work with non-numeric types, neither the array literal constructor nor the array subscripting operator have default implementations in Expression, however both of these are implemented in AnyExpression's standard symbol library.

Performance

Caching

By default, Expression caches parsed expressions. The expression cache is unlimited in size. In most applications this is very unlikely to ever be a problem - expressions are tiny, and even the most complex expression you can imagine is probably well under 1KB, so it would take a lot of them to cause memory pressure - But if for some reason you do ever need to reclaim the memory used by cached expressions, you can do so by calling the flushCache() method:

Expression.flushCache())

The flushCache() method takes an optional string argument, so you can also remove a specific expression from the cache without clearing others:

Expression.flushCache(for: "foo + bar"))

If you'd prefer even more fine-grained control of caching, you can pre-parse the expression without caching it, then create the Expression instance from the pre-parsed expression, as follows:

let expressionString = "foo + bar"
let parsedExpression = Expression.parse(expressionString, usingCache: false)
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])

By setting the usingCache argument to false in the code above, we avoid adding the expression to the global cache. You are also free to implement your own caching by storing the parsed expression and re-using it, which may be more efficient than the built-in cache in some cases (e.g. by avoiding thread management if your code is single-threaded).

A second variant of the Expression.parse() method accepts a String.UnicodeScalarView.SubSequence and optional list of terminating delimiter strings. This can be used to match an expression embedded inside a longer string, and leaves the startIndex of the character sequence in the right place to continue parsing once the delimiter is reached:

let expressionString = "lorem ipsum {foo + bar} dolor sit"
var characters = String.UnicodeScalarView.SubSequence(expression.unicodeScalars)
while characters.popFirst() != "{" {} // Read up to start of expression
let parsedExpression = Expression.parse(&characters, upTo: "}")
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])

Optimization

By default, expressions are optimized where possible to make evaluation more efficient. Common optimizations include replacing constants with their literal values, and replacing pure functions or operators with their result when all arguments are constant.

The optimizer reduces evaluation time at the cost of increased initialization time, and for an expression that will only be evaluated once or twice this tradeoff may not be worth it, in which case you can disable optimization using the options argument:

let expression = Expression("foo + bar", options: .noOptimize, ...)

On the other hand, if your expressions are being evaluated hundreds or thousands of times, you will want to take full advantage of the optimizer to improve your application's performance. To ensure you are getting the best out of Expression's optimizer, follow these guidelines:

Always pass constant values via the constants or arrays arguments instead of as a variable in the symbols dictionary. Constant values can be inlined, whereas variables must be re-computed each time the function is evaluated in case they have changed.

If your custom functions and operators are all pure - i.e. they have no side effects and always return the same output for a given set of argument values - then you should set the pureSymbols option for your expression. This option tells the optimizer that it's safe to inline any functions or operators in the symbols dictionary if all their arguments are constant. Note that the pureSymbols option does not affect variables or array symbols, which are never inlined.

If your expressions may contain values which are constant, but where not all possible values can be computed in advance - e.g. encoded values such as in the hex colors example, or arbitrary key paths that must be looked up in a deep object graph - you can use the init(pureSymbols:) initializer to decode or look up just the specific values that are needed.

Standard Library

Math Symbols

By default, Expression supports a number of basic math functions, operators, and constants that are generally useful, independent of any particular application.

If you use a custom symbol dictionary, you can override any default symbol, or overload default functions with different numbers of arguments (arity). Any symbols from the standard library that you do not explicitly override will still be available.

To explicitly disable individual symbols from the standard library, you can override them and throw an exception:

let expression = Expression("pow(2,3)", symbols: [
    .function("pow", arity: 2): { _ in throw Expression.Error.undefinedSymbol(.function("pow", arity: 2)) }
])
try expression.evaluate() // this will throw an error because pow() has been undefined

If you are using the init(impureSymbols:pureSymbols:) initializer, you can fall back to the standard library functions and operators by returning nil for unrecognized symbols. If you do not want to provide access to the standard library functions in your expression, throw an error for unrecognized symbols instead of returning nil.

let expression = Expression("3 + 4", pureSymbols: { symbol in
    switch symbol {
    case .function("foo", arity: 1):
        return { args in args[0] + 1 }
    default:
        return { _ in throw Expression.Error.undefinedSymbol(symbol) }
    }
})
try expression.evaluate() // this will throw an error because no standard library operators are supported, including +

Here are the currently supported math symbols:

constants

pi

infix operators

+ - / * %

prefix operators

-

functions

// Unary functions

sqrt(x)
floor(x)
ceil(x)
round(x)
cos(x)
acos(x)
sin(x)
asin(x)
tan(x)
atan(x)
abs(x)

// Binary functions

pow(x,y)
atan2(x,y)
mod(x,y)

// Variadic functions

max(x,y,[...])
min(x,y,[...])

Boolean Symbols

In addition to math, Expression also supports boolean logic, following the C convention that zero is false and any nonzero value is true. The standard boolean symbols are not enabled by default, but you can enable them using the .boolSymbols option:

let expression = Expression("foo ? bar : baz", options: .boolSymbols, ...)

As with the math symbols, all standard boolean operators can be individually overridden or disabled for a given expression using the symbols initializer argument.

Here are the currently supported boolean symbols:

constants

true
false

infix operators

==
!=
>
>=
<
<=
&&
||

prefix operators

!

ternary operator

?:

AnyExpression

Usage

AnyExpression is used in almost the exact same way as the Expression class, with the following exceptions:

  • AnyExpression's SymbolEvaluator functions accept and return Any instead of Double
  • Boolean symbols and operators are enabled by default when you create an AnyExpression
  • There is no separate arrays argument for the AnyExpression constructor. If you wish to pass an array or dictionary constant, you can add it to the constants dictionary like any other value type
  • AnyExpression supports [anonymous functions[(#anonymous-functions), which can be any value of type Expression.SymbolEvaluator or AnyExpression.SymbolEvaluator
  • You can also pass Expression.SymbolEvaluator or AnyExpression.SymbolEvaluator functions into AnyExpression using the constants dictionary, and these will behave just like ordinary function symbols

You can create and evaluate an AnyExpression instance as follows:

let expression = AnyExpression("'hello' + 'world'")
let result: String = try expression.evaluate() // 'helloworld'

Note the use of single quotes (') for string literals. AnyExpression supports single or double quotes for string literals. There is no difference between these, except that single quotes do not need to be escaped inside a Swift string literal.

Since AnyExpression's evaluate() method has a generic return type, you will need to tell it the expected type. In the example above, we did this by specifying an explicit type for the result variable, but you could also do it by using the as operator (without ! or ?):

let result = try expression.evaluate() as String

The evaluate function has a certain amount of built-in leniency with respect to types, so if (for example) the expression returns a boolean, but you specify Double as the expected type, the type will be converted automatically, but if it returns a string and you ask for Bool then a type mismatch error will be thrown.

The currently supported automatic conversions are:

  • T -> Optional
  • Numeric -> Numeric
  • Array -> Array
  • Numeric -> Bool
  • Bool -> Numeric
  • Any -> String

Symbols

In addition to adding support for string literals, AnyExpression extends Expression's standard library with some additional symbols for dealing with Optionals and null values:

  • nil - the null literal
  • ?? - the null coalescing operator

Optional unwrapping is automatic, so there is currently no need for the postfix ? or ! operators. nil (aka Optional.none) and NSNull are both treated the same way to avoid confusion when working with JSON or Objective-C API data.

Comparison operators like == and !=are also extended to work with anyHashabletype, and+` can be used for string concatenation, as in the example above.

For array symbols, AnyExpression can use any Hashable type as the index. This means that AnyExpression can work with Dictionary values as well as Arrays and ArraySlices.

Literals

As mentioned above, AnyExpression supports the use of quoted string literals, delimited with either single quotes (') or double quotes ("). Special characters inside the string can be escaped using a backlash ().

AnyExpression supports array literals defined in square brackets, e.g. [1, 2, 3] or ['foo', 'bar', 'baz']. Array literals can contain a mixture of value types and/or sub-expressions.

You can also create range literals using the ..< and ... syntaxes. Closed, half-open and partial ranges are supported. Ranges work with either Int or String.Index values, and can be used in conjunction with subscripting syntax for slicing arrays and strings.

Anonymous Functions

In addition to ordinary named function symbols, AnyExpression also calling anonymous functions, which are values of type Expression.SymbolEvaluator or AnyExpression.SymbolEvaluator that can be stored in a constant or returned from a sub-expression.

You can pass anonymous functions into AnyExpression by using a constant value instead of a .function() symbol, but note that this approach does not allow you the option of overloading functions with the same name by arity.

Unlike function symbols, anonymous functions do not support overloading, but you can use a switch inside the function body to implement different behaviors depending on the number of arguments. You should also throw an arityMismatch error if an unsupported number of arguments is passed, as this cannot be detected automatically, e.g.

let bar = { (args: [Any] throws -> Any in
    switch args.count {
    case 1:
        // behavior 1
    case 2:
        // behavior 2
    default:
        throw Expression.Error.arityMismatch(.function("bar", arity: 2))
    }
}

// static function foo returns anonymous function bar, which is called in the expression
let expression = AnyExpression("foo()(2)", symbols: [
    .function("foo"): { _ in bar }
])

Note: anonymous functions are assumed to be impure, so they are never eligible for inlining, regardless of whether you use the pureSymbols option.

Linux Support

AnyExpression works on Linux, with the following caveat:

  • AnyExpression doesn't support NSString bridging due to a limitation of Linux Foundation. If you want to use AnyExpression with NSString values then you'll have to manually convert them to String before and after evaluation.

Example Projects

Benchmark

The Benchmark app runs a set of test expressions using Expression, AnyExpression, NSExpression and JavaScriptCore's JSContext respectively. It then times how long it takes to parse and evaluate the expressions, and displays the result in a table.

Times are shown in either microseconds (µs) or milliseconds. The fastest result in each category is displayed in green, and the slowest in red.

For accurate results, the Benchmark app should be run in release mode on a real device. You can pull down on the table to refresh the test results. Tests are run on the main thread, so don't be surprised if the display locks up briefly while refreshing.

In my own tests, Expression was consistently the fastest implementation, and JavaScriptCore was consistently the slowest, both for initial setup and for evaluation once the context has been initialized.

Calculator

Not much to say about this. It's a calculator. You can type mathematical expressions into it, and it will evaluate them and produce a result (or an error, if what you typed was invalid).

Colors

The Colors example demonstrates how to use AnyExpression to create a (mostly) CSS-compliant color parser. It takes a string containing a named color, hex color or rgb() function call, and returns a UIColor object.

Layout

This is where things get interesting: The Layout example demonstrates a crude-but-usable layout system, which supports arbitrary expressions for the coordinates of the views.

It's conceptually similar to AutoLayout, but with some important differences:

  • The expressions can be as simple or as complex as you like. In AutoLayout every constraint uses a fixed formula, where only the operands are interchangeable.
  • Instead of applying an arbitrary number of constraints between properties of views, each view just has a size and position that can be calculated however you like.
  • Layout is deterministic. There is no weighting system used for resolving conflicts, and circular references are forbidden. Weighted relationships can be achieved using explicit multipliers.

Default layout values for the example views have been set in the Storyboard, but you can edit them live in the app by tapping a view and typing in new values.

Here are some things to note:

  • Every view has a top, left, width and height expression to define its coordinates on the screen.
  • Views have an optional key (like a tag, but string-based) that can be used to reference their properties from another view.
  • Any expression-based property of any view can reference any other property (of the same view, or any other view), and can even reference multiple properties.
  • Every view has a bottom and right property. These are computed, and cannot be set directly, but they can be used in expressions.
  • Circular references (a property whose value depends on itself) are forbidden, and will be detected by the system.
  • The width and height properties can use the auto variable, which does nothing useful for ordinary views, but can be used with text labels to calculate the optimal height for a given width, based on the amount of text.
  • Numeric values are measured in screen points. Percentage values are relative to the superview's width or height property.
  • Remember you can use functions like min() and max() to ensure that relative values don't go above or below a fixed threshold.

This is just a toy example, but if you like the concept check out the Layout framework on Github, which takes this idea to the next level.

REPL

The Expression REPL (Read Evaluate Print Loop) is a Mac command-line tool for evaluating expressions. Unlike the Calculator example, the REPL is based on AnyExpression, so it allows the use of any type that can be represented as a literal in Expression syntax - not just numbers.

Each line you type into the REPL is evaluated independently. To share values between expressions, you can define variables using an identifier name followed by = and then an expression, e.g:

foo = (5 + 6) + 7

The named variable ("foo", in this case) is then available to use in subsequent expressions.

Author: nicklockwood
Source Code: https://github.com/nicklockwood/Expression
License: MIT license

#ios #swift 

Expression: A Swift Framework for Evaluating Expressions At Runtime
Jamel  O'Reilly

Jamel O'Reilly

1660443960

VectorMath: A Swift Library That Implements Common 2D and 3D Vector

Purpose

VectorMath is a Swift library for Mac and iOS that implements common 2D and 3D vector and matrix functions, useful for games or vector-based graphics.

VectorMath takes advantage of Swift language features such as function and operator overloading and struct methods to provide a more elegant interface than most C, C++ or Cocoa-based graphics APIs.

VectorMath also provides a handy replacement for the GLKit vector math types and functions, which are not available yet in Swift due to their reliance on union types.

VectorMath is a completely standalone library, relying only on the Foundation framework. However, it provides optional compatibility extensions for SceneKit and Quartz (CoreGraphics/CoreAnimation) for interoperability with UIKit, AppKit, SpriteKit and SceneKit.

VectorMath is designed to be efficient, but has not been heavily optimized yet, and does not yet take advantage of architecture-specific hardware acceleration using the Accelerate framework.

Supported OS & SDK Versions

  • Supported build target - iOS 12.0, Mac OS 10.14 (Xcode 11.1)
  • Earliest supported deployment target - iOS 9.0, Mac OS 10.13
  • Earliest compatible deployment target - iOS 7.0, Mac OS 10.9

NOTE: 'Supported' means that the library has been tested with this version. 'Compatible' means that the library should work on this OS version (i.e. it doesn't rely on any unavailable SDK features) but is no longer being tested for compatibility and may require tweaking or bug fixes to run correctly.

Installation

To use the VectorMath functions in an app, drag the VectorMath.swift file (demo/test files and assets are not needed) into your project. You may also wish to include the VectorMath+SceneKit.swift and/or VectorMath+Quartz.swift compatibility extensions.

Types

VectorMath declares the following types:

Scalar

This is a typealias used for the scalar floating point values in the VectorMath library. It is set to Float by default, but you can change it to Double or CGFloat to improve performance for your specific application.

Vector2
Vector3
Vector4

These represent 2D, 3D and 4D vectors, respectively.

Matrix3
Matrix4

These represent homogenous 3x3 and 4x4 transform matrices, respectively.

Quaternion

This represents a rotation in 3D space. It has the same structure as Vector4D, but is defined as a different type due to the different use cases and methods.

All the VectorMath types conform to Equatable and Hashable, so they can be stored in Swift dictionaries.

Constants

VectorMath declares a number of namespaced constants for your convenience. They are as follows:

Scalar.pi
Scalar.halfPi
Scalar.quarterPi
Scalar.twoPi

These should be self-explanatory.

Scalar.degreesPerRadian
Scalar.radiansPerDegree

Conversion factors between degrees and radians. E.g. to convert 40 degrees to radians, you would say let r = 40 * .degreesPerRadian, or to convert Pi/2 radians to degrees, say let d = .halfPi * .radiansPerDegree

Scalar.epsilon = 0.0001

This is a floating point error value used by the approx-equal operator. You can change this if it's insufficiently (or excessively) precise for your needs.

Vector2.zero
Vector3.zero
Vector4.zero
Quaternion.Zero

These are zero vector constants, useful as default values for vectors

Vector2.x
Vector2.y
Vector3.x
Vector3.y
Vector3.z
Vector4.x
Vector4.y
Vector4.z
Vector4.w

These are unit vectors along various axes. For example Vector3.z has the value Vector3(0, 0, 1)

Matrix3.identity
Matrix4.identity
Quaternion.identity

These are identity matrices, which have the property that multiplying them by another matrix or vector has no effect.

Methods

The complete list of VectorMath properties and methods is given below. These are mostly self-explanatory. If you can't find a method you are looking for (e.g. a method to rotate a vector using a quaternion), it's probably implemented as an operator (see "Operators" below).

Vector2
    init(x: Scalar, y: Scalar)
    init(_: Scalar, _: Scalar)
    init(_: [Scalar])
    lengthSquared: Scalar
    length: Scalar
    inverse: Vector2
    toArray() -> [Scalar]
    dot(Vector2) -> Scalar
    cross(Vector2) -> Scalar
    normalized() -> Vector2
    rotated(by: Scalar) -> Vector2
    rotated(by: Scalar, around: Vector2) -> Vector2
    angle(with: Vector2) -> Scalar
    interpolated(with: Vector2, by: Scalar) -> Vector2

Vector3
    init(x: Scalar, y: Scalar, z: Scalar)
    init(_: Scalar, _: Scalar, _: Scalar)
    init(_: [Scalar])
    lengthSquared: Scalar
    length: Scalar
    inverse: Vector3
    xy: Vector2
    xz: Vector2
    yz: Vector2
    toArray() -> [Scalar]
    dot(Vector3) -> Scalar
    cross(Vector3) -> Vector3
    normalized() -> Vector3
    interpolated(with: Vector3, by: Scalar) -> Vector3

Vector4
    init(x: Scalar, y: Scalar, z: Scalar, w: Scalar)
    init(_: Scalar, _: Scalar, _: Scalar, _: Scalar)
    init(_: Vector3, w: Scalar)
    init(_: [Scalar])
    lengthSquared: Scalar
    length: Scalar
    inverse: Vector4
    xyz: Vector3
    xy: Vector2
    xz: Vector2
    yz: Vector2
    toArray() -> [Scalar]
    toVector3() -> Vector3
    dot(Vector4) -> Scalar
    normalized() -> Vector4
    interpolated(with: Vector4, by: Scalar) -> Vector4

Matrix3
    init(m11: Scalar, m12: Scalar, ... m33: Scalar)
    init(_: Scalar, _: Scalar, ... _: Scalar)
    init(scale: Vector2)
    init(translation: Vector2)
    init(rotation: Scalar)
    init(_: [Scalar])
    adjugate: Matrix3
    determinant: Scalar
    transpose: Matrix3
    inverse: Matrix3
    toArray() -> [Scalar]
    interpolated(with: Matrix3, by: Scalar) -> Matrix3

Matrix4
    init(m11: Scalar, m12: Scalar, ... m33: Scalar)
    init(_: Scalar, _: Scalar, ... _: Scalar)
    init(scale: Vector3)
    init(translation: Vector3)
    init(rotation: Vector4)
    init(quaternion: Quaternion)
    init(fovx: Scalar, fovy: Scalar, near: Scalar, far: Scalar)
    init(fovx: Scalar, aspect: Scalar, near: Scalar, far: Scalar)
    init(fovy: Scalar, aspect: Scalar, near: Scalar, far: Scalar)
    init(top: Scalar, right: Scalar, bottom: Scalar, left: Scalar, near: Scalar, far: Scalar)
    init(_: [Scalar])
    adjugate: Matrix4
    determinant: Scalar
    transpose: Matrix4
    inverse: Matrix4
    toArray() -> [Scalar]
    interpolated(with: Matrix3, by: Scalar) -> Matrix3

Quaternion
    init(x: Scalar, y: Scalar, z: Scalar, w: Scalar)
    init(_: Scalar, _: Scalar, _: Scalar, _: Scalar)
    init(axisAngle: Vector4)
    init(pitch: Scalar, yaw: Scalar, roll: Scalar)
    init(rotationMatrix m: Matrix4)
    init(_: [Scalar])
    lengthSquared: Scalar
    length: Scalar
    inverse: Quaternion
    xyz: Vector3
    pitch: Scalar
    yaw: Scalar
    roll: Scalar
    toAxisAngle() -> Vector4
    toPitchYawRoll() -> (pitch: Scalar, yaw: Scalar, roll: Scalar)
    toArray() -> [Scalar]
    dot(Quaternion) -> Scalar
    normalized() -> Quaternion
    interpolated(with: Quaternion, by: Scalar) -> Quaternion

Operators

VectorMath makes extensive use of operator overloading, but I've tried not to go overboard with custom operators. The only nonstandard operator defined is ~=, meaning "approximately equal", which is extremely useful for comparing Scalar, Vector or Matrix values for equality, as, due to floating point imprecision, they are rarely identical.

The *, /, +, - and == operators are implemented for most of the included types. * in particular is useful for matrix and vector transforms. For example, to apply a matrix transform "m" to a vector "v" you can write m * v. * can also be used in conjunction with a Scalar value to scale a vector.

Unary minus is supported for inversion/negation on vectors and matrices.

Dot product, cross product and normalization are not available in operator form, but are supplied as methods on the various types.

Author: nicklockwood
Source Code: https://github.com/nicklockwood/VectorMath
License: MIT license

#ios #swift 

VectorMath: A Swift Library That Implements Common 2D and 3D Vector
Jamel  O'Reilly

Jamel O'Reilly

1660429380

A Collection Of Functions for Statistical Calculation Written in Swift

σ (sigma) - statistics library written in Swift

This library is a collection of functions that perform statistical calculations in Swift. It can be used in Swift apps for Apple devices and in open source Swift programs on other platforms.

Setup

There are four ways you can add Sigma to your project.

Add source (iOS 7+)

Simply add SigmaDistrib.swift file to your project.

Setup with Carthage (iOS 8+)

Alternatively, add github "evgenyneu/SigmaSwiftStatistics" ~> 9.0 to your Cartfile and run carthage update.

Setup with CocoaPods (iOS 8+)

If you are using CocoaPods add this text to your Podfile and run pod install.

use_frameworks!
target 'Your target name'
pod 'SigmaSwiftStatistics', '~> 9.0'

Setup with Swift Package Manager

Legacy Swift versions

Setup a previous version of the library if you use an older version of Swift.

Usage

Add import SigmaSwiftStatistics to your source code unless you used the file setup method.

Average / mean

Computes arithmetic mean of values in the array.

Note:

  • Returns nil for an empty array.
  • Same as AVERAGE in Microsoft Excel and Google Docs Sheets.

Formula

A = Σ(x) / n

Where:

  • n is the number of values.
Sigma.average([1, 3, 8])
// Result: 4

Central moment

Computes central moment of the dataset.

Note:

  • Returns nil for an empty array.
  • Same as in Wolfram Alpha and "moments" R package.

Formula

Σ(x - m)^k / n

Where:

  • m is the sample mean.
  • k is the order of the moment (0, 1, 2, 3, ...).
  • n is the sample size.
Sigma.centralMoment([3, -1, 1, 4.1, 4.1, 0.7], order: 3)
// Result: -1.5999259259

Covariance of a population

Computes covariance of the entire population between two variables: x and y.

Note:

  • Returns nil if arrays x and y have different number of values.
  • Returns nil for empty arrays.
  • Same as COVAR and COVARIANCE.P functions in Microsoft Excel and COVAR in Google Docs Sheets.

Formula

cov(x,y) = Σ(x - mx)(y - my) / n

Where:

  • mx is the population mean of the first variable.
  • my is the population mean of the second variable.
  • n is the total number of values.
let x = [1, 2, 3.5, 3.7, 8, 12]
let y = [0.5, 1, 2.1, 3.4, 3.4, 4]
Sigma.covariancePopulation(x: x, y: y)
// Result: 4.19166666666667

Covariance of a sample

Computes sample covariance between two variables: x and y.

Note:

  • Returns nil if arrays x and y have different number of values.
  • Returns nil for empty arrays or arrays containing a single element.
  • Same as COVARIANCE.S function in Microsoft Excel.

Formula

cov(x,y) = Σ(x - mx)(y - my) / (n - 1)

Where:

  • mx is the sample mean of the first variable.
  • my is the sample mean of the second variable.
  • n is the total number of values.
let x = [1, 2, 3.5, 3.7, 8, 12]
let y = [0.5, 1, 2.1, 3.4, 3.4, 4]
Sigma.covarianceSample(x: x, y: y)
// Result: 5.03

Coefficient of variation of a sample

Computes coefficient of variation based on a sample.

Note:

  • Returns nil when the array is empty or contains a single value.
  • Returns Double.infinity if the mean is zero.
  • Same as in Wolfram Alfa and in "raster" R package (expressed as a percentage in "raster").

Formula

CV = s / m

Where:

  • s is the sample standard deviation.
  • m is the mean.
Sigma.coefficientOfVariationSample([1, 12, 19.5, -5, 3, 8])
// Result: 1.3518226672

Frequencies

Returns a dictionary with the keys containing the numbers from the input array and the values corresponding to the frequencies of those numbers.

Sigma.frequencies([1, 2, 3, 4, 5, 4, 4, 3, 5])
// Result: [2:1, 3:2, 4:3, 5:2, 1:1]

Kurtosis A

Returns the kurtosis of a series of numbers.

Note:

  • Returns nil if the dataset contains less than 4 values.
  • Returns nil if all the values in the dataset are the same.
  • Same as KURT in Microsoft Excel and Google Docs Sheets.

Formula

Kurtosis formula

Sigma.kurtosisA([2, 1, 3, 4.1, 19, 1.5])
// Result: 5.4570693277

Kurtosis B

Returns the kurtosis of a series of numbers.

Note:

  • Returns nil if the dataset contains less than 2 values.
  • Returns nil if all the values in the dataset are the same.
  • Same as in Wolfram Alpha and "moments" R package.

Formula

Kurtosis formula

Sigma.kurtosisB([2, 1, 3, 4.1, 19, 1.5])
// Result: 4.0138523409

Max

Returns the maximum value in the array.

Note: returns nil for an empty array.

Sigma.max([1, 8, 3])
// Result: 8

Median

Returns the median value from the array.

Note:

  • Returns nil when the array is empty.
  • Returns the mean of the two middle values if there is an even number of items in the array.
  • Same as MEDIAN in Microsoft Excel and Google Docs Sheets.
Sigma.median([1, 12, 19.5, 3, -5])
// Result: 3

Median high

Returns the median value from the array.

Note:

  • Returns nil when the array is empty.
  • Returns the higher of the two middle values if there is an even number of items in the array.
Sigma.medianHigh([1, 12, 19.5, 10, 3, -5])
// Result: 10

Median low

Returns the median value from the array.

Note:

  • Returns nil when the array is empty.
  • Returns the lower of the two middle values if there is an even number of items in the array.
Sigma.medianLow([1, 12, 19.5, 10, 3, -5])
// Result: 3

Min

Returns the minimum value in the array.

Note: returns nil for an empty array.

Sigma.min([7, 2, 3])
// Result: 2

Normal distribution

Returns the normal distribution for the given values of x, μ and σ. The returned value is the area under the normal curve to the left of the value x.

Note:

  • Returns nil if σ is zero or negative.
  • Defaults: μ = 0, σ = 1.
  • Same as NORM.S.DIST, NORM.DIST and NORMDIST Excel functions and NORMDIST function in Google Docs sheet with cumulative argument equal to true.
Sigma.normalDistribution(x: -1, μ: 0, σ: 1)
// Result: 0.1586552539314570

Normal density

Returns density of the normal function for the given values of x, μ and σ.

Note:

  • Returns nil if σ is zero or negative.
  • Defaults: μ = 0, σ = 1.
  • Same as NORM.S.DIST, NORM.DIST and NORMDIST Excel functions and NORMDIST function in Google Docs sheet with cumulative argument equal to false.

Formula

Nodemal density function

Where:

  • x is the input value of the normal density function.
  • μ is the mean.
  • σ is the standard deviation.
Sigma.normalDensity(x: 0, μ: 0, σ: 1)
// Result: 0.3989422804014327

Normal quantile

Returns the quantile function for the normal distribution (the inverse of normal distribution). The p argument is the probability, or the area under the normal curve to the left of the returned value.

Note:

  • Returns nil if σ is zero or negative.
  • Returns nil if p is negative or greater than one.
  • Returns -Double.infinity if p is zero, and Double.infinity if p is one.
  • Defaults: μ = 0, σ = 1.
  • Same as NORM.INV, NORM.S.INV and NORMINV Excel functions and NORMINV, NORMSINV Google Docs sheet functions.
Sigma.normalQuantile(p: 0.025, μ: 0, σ: 1)
// -1.9599639845400538

Pearson correlation coefficient

Calculates the Pearson product-moment correlation coefficient between two variables: x and y.

Note:

  • Returns nil if arrays x and y have different number of values.
  • Returns nil for empty arrays.
  • Same as CORREL and PEARSON functions in Microsoft Excel and Google Docs Sheets.

Formula

p(x,y) = cov(x,y) / (σx * σy)

Where:

  • cov is the population covariance.
  • σ is the population standard deviation.
let x = [1, 2, 3.5, 3.7, 8, 12]
let y = [0.5, 1, 2.1, 3.4, 3.4, 4]
Sigma.pearson(x: x, y: y)
// Result: 0.843760859352745

Percentile

Calculates the Percentile value for the given dataset.

Note:

  • Returns nil when the values array is empty.
  • Returns nil when supplied percentile parameter is negative or greater than 1.
  • Same as PERCENTILE or PERCENTILE.INC in Microsoft Excel and PERCENTILE in Google Docs Sheets.
  • Same as the 7th sample quantile method from the Hyndman and Fan paper (1996).

See the Percentile method documentation for more information.

// Calculate 40th percentile
Sigma.percentile([35, 20, 50, 40, 15], percentile: 0.4)
// Result: 29

// Same as
Sigma.quantiles.method7([35, 20, 50, 40, 15], probability: 0.4)

Quantiles

Collection of nine functions that calculate sample quantiles corresponding to the given probability. This is an implementation of the nine algorithms described in the Hyndman and Fan paper (1996). The documentation of the functions is based on R and Wikipedia.

Note:

  • Returns nil if the dataset is empty.
  • Returns nil if the probability is outside the [0, 1] range.
  • Same as quantile function in R.

Quantile method 1

This method calculates quantiles using the inverse of the empirical distribution function.

Sigma.quantiles.method1([1, 12, 19.5, -5, 3, 8], probability: 0.5)
// Result: 3

Quantile method 2

This method uses inverted empirical distribution function with averaging.

Sigma.quantiles.method2([1, 12, 19.5, -5, 3, 8], probability: 0.5)
// Result: 5.5

Quantile method 3

Sigma.quantiles.method3([1, 12, 19.5, -5, 3, 8], probability: 0.5)
// Result: 3

Quantile method 4

The method uses linear interpolation of the empirical distribution function.

Sigma.quantiles.method4([1, 12, 19.5, -5, 3, 8], probability: 0.17)
// Result: -4.88

Quantile method 5

This method uses a piecewise linear function where the knots are the values midway through the steps of the empirical distribution function.

Sigma.quantiles.method5([1, 12, 19.5, -5, 3, 8], probability: 0.11)
// Result: -4.04

Quantile method 6

This method is implemented in Microsoft Excel (PERCENTILE.EXC), Minitab and SPSS. It uses linear interpolation of the expectations for the order statistics for the uniform distribution on [0,1].

Sigma.quantiles.method6([1, 12, 19.5, -5, 3, 8], probability: 0.1999)
// Result: -2.6042

Quantile method 7

This method is implemented in S, Microsoft Excel (PERCENTILE or PERCENTILE.INC) and Google Docs Sheets (PERCENTILE). It uses linear interpolation of the modes for the order statistics for the uniform distribution on [0, 1].

Sigma.quantiles.method7([1, 12, 19.5, -5, 3, 8], probability: 0.00001)
// Result: -4.9997

Quantile method 8

The quantiles returned by the method are approximately median-unbiased regardless of the distribution of x.

Sigma.quantiles.method8([1, 12, 19.5, -5, 3, 8], probability: 0.11)
// Result: -4.82

Quantile method 9

The quantiles returned by this method are approximately unbiased for the expected order statistics if x is normally distributed.

Sigma.quantiles.method9([1, 12, 19.5, -5, 3, 8], probability: 0.10001)
// Result: -4.999625

Rank

Returns the ranks of the values in the dataset.

Note:

Receives an optional ties parameter that determines how the ranks for the equal values ('ties') are calculated. Default value is .average. Possible values:

  • .average: uses the average rank. Same as RANK.AVG in Microsoft Excel and Google Docs Sheets.
  • .min, .max: uses the minimum/maximum rank. The value .min is the same as RANK and RANK.EQ in Microsoft Excel and Google Docs Sheets.
  • .first, .last: the ranks are incremented/decremented.

Same as rank function in R.

Sigma.rank([2, 3, 6, 5, 3], ties: .average)
// Result: [1.0, 2.5, 5.0, 4.0, 2.5]

Skewness A

Returns the skewness of the dataset.

Note:

  • Returns nil if the dataset contains less than 3 values.
  • Returns nil if all the values in the dataset are the same.
  • Same as SKEW in Microsoft Excel and Google Docs Sheets.

Formula

Skewness formula

Sigma.skewnessA([4, 2.1, 8, 21, 1])
// Result: 1.6994131524

Skewness B

Returns the skewness of the dataset.

Note:

  • Returns nil if the dataset contains less than 3 values.
  • Returns nil if all the values in the dataset are the same.
  • Same as in Wolfram Alpha, SKEW.P in Microsoft Excel and skewness function in "moments" R package.

Formula

Skewness formula

Sigma.skewnessB([4, 2.1, 8, 21, 1])
// Result: 1.1400009992

Standard deviation of a population

Computes standard deviation of entire population.

Note:

  • Returns nil for an empty array.
  • Same as STDEVP and STDEV.P in Microsoft Excel and STDEVP in Google Docs Sheets.

Formula

σ = sqrt( Σ( (x - m)^2 ) / n )

Where:

  • m is the population mean.
  • n is the population size.
Sigma.standardDeviationPopulation([1, 12, 19.5, -5, 3, 8])
// Result: 7.918420858282849

Standard deviation of a sample

Computes standard deviation based on a sample.

Note:

  • Returns nil when the array is empty or contains a single value.
  • Same as STDEV and STDEV.S in Microsoft Excel and STDEV in Google Docs Sheets.

Formula

s = sqrt( Σ( (x - m)^2 ) / (n - 1) )

Where:

  • m is the sample mean.
  • n is the sample size.
Sigma.standardDeviationSample([1, 12, 19.5, -5, 3, 8])
// Result: 8.674195447801869

Standard error of the mean

Computes standard error of the mean.

Note:

  • Returns nil when the array is empty or contains a single value.

Formula

SE = s / sqrt(n)

Where:

  • s is the sample standard deviation.
  • n is the sample size.
Sigma.standardErrorOfTheMean([1, 12, 19.5, -5, 3, 8])
// Result: 3.5412254627

Sum

Computes sum of values from the array.

Sigma.sum([1, 3, 8])
// Result: 12

Unique values

Returns an unsorted array containing all values that occur within the input array without the duplicates.

Sigma.uniqueValues([2, 1, 3, 4, 5, 4, 3, 5])
// Result: [2, 3, 4, 5, 1]

Variance of a population

Computes variance of entire population.

Note:

  • Returns nil when the array is empty.
  • Same as VAR.P or VARPA in Microsoft Excel and VARP or VARPA in Google Docs Sheets.

Formula

σ^2 = Σ( (x - m)^2 ) / n

Where:

  • m is the population mean.
  • n is the population size.
Sigma.variancePopulation([1, 12, 19.5, -5, 3, 8])
// Result: 62.70138889

Variance of a sample

Computes variance based on a sample.

Note:

  • Returns nil when the array is empty or contains a single value.
  • Same as VAR, VAR.S or VARA in Microsoft Excel and VAR or VARA in Google Docs Sheets.

Formula

s^2 = Σ( (x - m)^2 ) / (n - 1)

Where:

  • m is the sample mean.
  • n is the sample size.
Sigma.varianceSample([1, 12, 19.5, -5, 3, 8])
// Result: 75.24166667

Author: evgenyneu
Source Code: https://github.com/evgenyneu/SigmaSwiftStatistics
License: MIT license

#ios #swift 

A Collection Of Functions for Statistical Calculation Written in Swift
Jamel  O'Reilly

Jamel O'Reilly

1660414800

BigInt: Arbitrary-Precision Arithmetic in Pure Swift

Overview

This repository provides integer types of arbitrary width implemented in 100% pure Swift. The underlying representation is in base 2^64, using Array<UInt64>.

This module is handy when you need an integer type that's wider than UIntMax, but you don't want to add The GNU Multiple Precision Arithmetic Library as a dependency.

Two big integer types are included: BigUInt and BigInt, the latter being the signed variant. Both of these are Swift structs with copy-on-write value semantics, and they can be used much like any other integer type.

The library provides implementations for some of the most frequently useful functions on big integers, including

All functionality from Comparable and Hashable

The full set of arithmetic operators: +, -, *, /, %, +=, -=, *=, /=, %=

  • Addition and subtraction have variants that allow for shifting the digits of the second operand on the fly.
  • Unsigned subtraction will trap when the result would be negative. (There are variants that return an overflow flag.)
  • Multiplication uses brute force for numbers up to 1024 digits, then switches to Karatsuba's recursive method. (This limit is configurable, see BigUInt.directMultiplicationLimit.)
  • A fused multiply-add method is also available, along with other special-case variants.
  • Division uses Knuth's Algorithm D, with its 3/2 digits wide quotient approximation. It will trap when the divisor is zero.
  • BigUInt.divide returns the quotient and remainder at once; this is faster than calculating them separately.

Bitwise operators: ~, |, &, ^, |=, &=, ^=, plus the following read-only properties:

  • bitWidth: the minimum number of bits required to store the integer,
  • trailingZeroBitCount: the number of trailing zero bits in the binary representation,
  • leadingZeroBitCount: the number of leading zero bits (when the last digit isn't full),

Shift operators: >>, <<, >>=, <<=

Methods to convert NSData to big integers and vice versa.

Support for generating random integers of specified maximum width or magnitude.

Radix conversion to/from Strings and big integers up to base 36 (using repeated divisions).

  • Big integers use this to implement StringLiteralConvertible (in base 10).

sqrt(n): The square root of an integer (using Newton's method).

BigUInt.gcd(n, m): The greatest common divisor of two integers (Stein's algorithm).

base.power(exponent, modulus): Modular exponentiation (right-to-left binary method). Vanilla exponentiation is also available.

n.inverse(modulus): Multiplicative inverse in modulo arithmetic (extended Euclidean algorithm).

n.isPrime(): Miller–Rabin primality test.

The implementations are intended to be reasonably efficient, but they are unlikely to be competitive with GMP at all, even when I happened to implement an algorithm with same asymptotic behavior as GMP. (I haven't performed a comparison benchmark, though.)

The library has 100% unit test coverage. Sadly this does not imply that there are no bugs in it.

Requirements and Integration

BigInt 4.0.0 requires Swift 4.2 (The last version with support for Swift 3.x was BigInt 2.1.0. The last version with support for Swift 2 was BigInt 1.3.0.)

Swift Versionlast BigInt Version
3.x2.1.0
4.03.1.0
4.24.0.0
5.x5.3.0

BigInt deploys to macOS 10.10, iOS 9, watchOS 2 and tvOS 9. It has been tested on the latest OS releases only---however, as the module uses very few platform-provided APIs, there should be very few issues with earlier versions.

BigInt uses no APIs specific to Apple platforms, so it should be easy to port it to other operating systems.

Setup instructions:

Swift Package Manager: Although the Package Manager is still in its infancy, BigInt provides experimental support for it. Add this to the dependency section of your Package.swift manifest:

.package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0")

CocoaPods: Put this in your Podfile:

pod 'BigInt', '~> 5.3'

Carthage: Put this in your Cartfile:

github "attaswift/BigInt" ~> 5.3

Implementation notes

BigUInt is a MutableCollectionType of its 64-bit digits, with the least significant digit at index 0. As a convenience, BigUInt allows you to subscript it with indexes at or above its count. The subscript operator returns 0 for out-of-bound gets and automatically extends the array on out-of-bound sets. This makes memory management simpler.

BigInt is just a tiny wrapper around a BigUInt [absolute value][magnitude] and a sign bit, both of which are accessible as public read-write properties.

Why is there no generic BigInt<Digit> type?

The types provided by BigInt are not parametric—this is very much intentional, as Swift generics cost us dearly at runtime in this use case. In every approach I tried, making arbitrary-precision arithmetic operations work with a generic Digit type parameter resulted in code that was literally ten times slower. If you can make the algorithms generic without such a huge performance hit, please enlighten me!

This is an area that I plan to investigate more, as it would be useful to have generic implementations for arbitrary-width arithmetic operations. (Polynomial division and decimal bases are two examples.) The library already implements double-digit multiplication and division as extension methods on a protocol with an associated type requirement; this has not measurably affected performance. Unfortunately, the same is not true for BigUInt's methods.

Of course, as a last resort, we could just duplicate the code to create a separate generic variant that was slower but more flexible.

Calculation Samples

Obligatory Factorial Demo

It is easy to use BigInt to calculate the factorial function for any integer:

import BigInt

func factorial(_ n: Int) -> BigInt {
    return (1 ... n).map { BigInt($0) }.reduce(BigInt(1), *)
}

print(factorial(10))
==> 362880

print(factorial(100))
==> 93326215443944152681699238856266700490715968264381621468592963895217599993229915
    6089414639761565182862536979208272237582511852109168640000000000000000000000

print(factorial(1000))
==> 40238726007709377354370243392300398571937486421071463254379991042993851239862902
    05920442084869694048004799886101971960586316668729948085589013238296699445909974
    24504087073759918823627727188732519779505950995276120874975462497043601418278094
    64649629105639388743788648733711918104582578364784997701247663288983595573543251
    31853239584630755574091142624174743493475534286465766116677973966688202912073791
    43853719588249808126867838374559731746136085379534524221586593201928090878297308
    43139284440328123155861103697680135730421616874760967587134831202547858932076716
    91324484262361314125087802080002616831510273418279777047846358681701643650241536
    91398281264810213092761244896359928705114964975419909342221566832572080821333186
    11681155361583654698404670897560290095053761647584772842188967964624494516076535
    34081989013854424879849599533191017233555566021394503997362807501378376153071277
    61926849034352625200015888535147331611702103968175921510907788019393178114194545
    25722386554146106289218796022383897147608850627686296714667469756291123408243920
    81601537808898939645182632436716167621791689097799119037540312746222899880051954
    44414282012187361745992642956581746628302955570299024324153181617210465832036786
    90611726015878352075151628422554026517048330422614397428693306169089796848259012
    54583271682264580665267699586526822728070757813918581788896522081643483448259932
    66043367660176999612831860788386150279465955131156552036093988180612138558600301
    43569452722420634463179746059468257310379008402443243846565724501440282188525247
    09351906209290231364932734975655139587205596542287497740114133469627154228458623
    77387538230483865688976461927383814900140767310446640259899490222221765904339901
    88601856652648506179970235619389701786004081188972991831102117122984590164192106
    88843871218556461249607987229085192968193723886426148396573822911231250241866493
    53143970137428531926649875337218940694281434118520158014123344828015051399694290
    15348307764456909907315243327828826986460278986432113908350621709500259738986355
    42771967428222487575867657523442202075736305694988250879689281627538488633969099
    59826280956121450994871701244516461260379029309120889086942028510640182154399457
    15680594187274899809425474217358240106367740459574178516082923013535808184009699
    63725242305608559037006242712434169090041536901059339838357779394109700277534720
    00000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000

Well, I guess that's all right, but it's not very interesting. Let's try something more useful.

RSA Cryptography

The BigInt module provides all necessary parts to implement an (overly) simple RSA cryptography system.

Let's start with a simple function that generates a random n-bit prime. The module includes a function to generate random integers of a specific size, and also an isPrime method that performs the Miller–Rabin primality test. These are all we need:

func generatePrime(_ width: Int) -> BigUInt {
    while true {
        var random = BigUInt.randomInteger(withExactWidth: width)
        random |= BigUInt(1)
        if random.isPrime() {
            return random
        }
    }
}

let p = generatePrime(1024)
==> 13308187650642192396256419911012544845370493728424936791561478318443071617242872
    81980956747087187419914435169914161116601678883358611076800743580556055714173922
    08406194264346635072293912609713085260354070700055888678514690878149253177960273
    775659537560220378850112471985434373425534121373466492101182463962031

let q = generatePrime(1024)
==> 17072954422657145489547308812333368925007949054501204983863958355897172093173783
    10108226596943999553784252564650624766276133157586733504784616138305701168410157
    80784336308507083874651158029602582993233111593356512531869546706885170044355115
    669728424124141763799008880327106952436883614887277350838425336156327

Cool! Now that we have two large primes, we can produce an RSA public/private keypair out of them.

typealias Key = (modulus: BigUInt, exponent: BigUInt)

let n = p * q
==> 22721008120758282530010953362926306641542233757318103044313144976976529789946696
    15454966720907712515917481418981591379647635391260569349099666410127279690367978
    81184375533755888994370640857883754985364288413796100527262763202679037134021810
    57933883525572232242690805678883227791774442041516929419640051653934584376704034
    63953169772816907280591934423237977258358097846511079947337857778137177570668391
    57455417707100275487770399281417352829897118140972240757708561027087217205975220
    02207275447810167397968435583004676293892340103729490987263776871467057582629588
    916498579594964478080508868267360515953225283461208420137

let e: BigUInt = 65537
let phi = (p - 1) * (q - 1)
let d = e.inverse(phi)!     // d * e % phi == 1
==> 13964664343869014759736350480776837992604500903989703383202366291905558996277719
    77822086142456362972689566985925179681282432115598451765899180050962461295573831
    37069237934291884106584820998146965085531433195106686745474222222620986858696591
    69836532468835154412554521152103642453158895363417640676611704542784576974374954
    45789456921660619938185093118762690200980720312508614337759620606992462563490422
    76669559556568917533268479190948959560397579572761529852891246283539604545691244
    89999692877158676643042118662613875863504016129837099223040687512684532694527109
    80742873307409704484365002175294665608486688146261327793

let publicKey: Key = (n, e)
let privateKey: Key = (n, d)

In RSA, modular exponentiation is used to encrypt (and decrypt) messages.

func encrypt(_ message: BigUInt, key: Key) -> BigUInt {
    return message.power(key.exponent, modulus: key.modulus)
}

Let's try out our new keypair by converting a string into UTF-8, interpreting the resulting binary representation as a big integer, and encrypting it with the public key. BigUInt has an initializer that takes an NSData, so this is pretty easy to do:

let secret: BigUInt = BigUInt("Arbitrary precision arithmetic is fun!".dataUsingEncoding(NSUTF8StringEncoding)!)
==> 83323446846105976078466731524728681905293067701804838925389198929123912971229457
    68818568737

let cyphertext = encrypt(secret, key: publicKey)
==> 95186982543485985200666516508066093880038842892337880561554910904277290917831453
    54854954722744805432145474047391353716305176389470779020645959135298322520888633
    61674945129099575943384767330342554525120384485469428048962027149169876127890306
    77028183904699491962050888974524603226290836984166164759586952419343589385279641
    47999991283152843977988979846238236160274201261075188190509539751990119132013021
    74866638595734222867005089157198503204192264814750832072844208520394603054901706
    06024394731371973402595826456435944968439153664617188570808940022471990638468783
    49208193955207336172861151720299024935127021719852700882

Well, it looks encrypted all right, but can we get the original message back? In theory, encrypting the cyphertext with the private key returns the original message. Let's see:

let plaintext = encrypt(cyphertext, key: privateKey)
==> 83323446846105976078466731524728681905293067701804838925389198929123912971229457
    68818568737

let received = String(data: plaintext.serialize(), encoding: NSUTF8StringEncoding)
==> "Arbitrary precision arithmetic is fun!"

Yay! This is truly terrific, but please don't use this example code in an actual cryptography system. RSA has lots of subtle (and some not so subtle) complications that we ignored to keep this example short.

Calculating the Digits of π

Another fun activity to try with BigInts is to generate the digits of π. Let's try implementing Jeremy Gibbon's spigot algorithm. This is a rather slow algorithm as π-generators go, but it makes up for it with its grooviness factor: it's remarkably short, it only uses (big) integer arithmetic, and every iteration produces a single new digit in the base-10 representation of π. This naturally leads to an implementation as an infinite GeneratorType:

func digitsOfPi() -> AnyGenerator<Int> {
    var q: BigUInt = 1
    var r: BigUInt = 180
    var t: BigUInt = 60
    var i: UInt64 = 2 // Does not overflow until digit #826_566_842
    return AnyIterator {
        let u: UInt64 = 3 * (3 * i + 1) * (3 * i + 2)
        let y = (q.multiplied(byDigit: 27 * i - 12) + 5 * r) / (5 * t)
        (q, r, t) = (
            10 * q.multiplied(byDigit: i * (2 * i - 1)),
            10 * (q.multiplied(byDigit: 5 * i - 2) + r - y * t).multiplied(byDigit: u),
            t.multiplied(byDigit: u))
        i += 1
        return Int(y[0])
    }
}

Well, that was surprisingly easy. But does it work? Of course it does!

var digits = "π ≈ "
var count = 0
for digit in digitsOfPi() {
    assert(digit < 10)
    digits += String(digit)
    count += 1
    if count == 1 { digits += "." }
    if count == 1000 { break }
}

digits
==> π ≈ 3.14159265358979323846264338327950288419716939937510582097494459230781640628
    62089986280348253421170679821480865132823066470938446095505822317253594081284811
    17450284102701938521105559644622948954930381964428810975665933446128475648233786
    78316527120190914564856692346034861045432664821339360726024914127372458700660631
    55881748815209209628292540917153643678925903600113305305488204665213841469519415
    11609433057270365759591953092186117381932611793105118548074462379962749567351885
    75272489122793818301194912983367336244065664308602139494639522473719070217986094
    37027705392171762931767523846748184676694051320005681271452635608277857713427577
    89609173637178721468440901224953430146549585371050792279689258923542019956112129
    02196086403441815981362977477130996051870721134999999837297804995105973173281609
    63185950244594553469083026425223082533446850352619311881710100031378387528865875
    33208381420617177669147303598253490428755468731159562863882353787593751957781857
    780532171226806613001927876611195909216420198

Now go and have some fun with big integers on your own!

Author: attaswift
Source Code: https://github.com/attaswift/BigInt
License: MIT license

#ios #swift 

BigInt: Arbitrary-Precision Arithmetic in Pure Swift
Hunter  Krajcik

Hunter Krajcik

1660409996

Mobi_connect: A New Flutter Plugin for Showing toast in Android & iOS

mobi_connect

A new flutter plugin project.

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add mobi_connect

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  mobi_connect: ^0.0.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:mobi_connect/mobi_connect.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:mobi_connect/mobi_connect.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await MobiConnect.platformVersion ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Running on: $_platformVersion\n'),
              const SizedBox(height: 20),
              MaterialButton(
                child: const Text('Show Toast'),
                textColor: Colors.white,
                color: Theme.of(context).primaryColor,
                onPressed: () {
                  MobiConnect.showToast('Hello, I am a toast!');
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

Getting Started

This project is a starting point for a Flutter plug-in package, a specialized package that includes platform-specific implementation code for Android and/or iOS.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Download Details:

Author: Erpankajpatel
Source Code: https://github.com/erpankajpatel/mobi-package 
License: View license

#flutter #dart #ios #android 

Mobi_connect: A New Flutter Plugin for Showing toast in Android & iOS

A Paginated Live List in Flutter

A package that handles pagination, subscriptions to updates, updating pages, and error handling.

Getting Started

The package provides a set of widgets that takes a concrete PaginationController and implement a live list behavior by controlling loading of items when needed, subscriptions to pages updates, updating the list and notifying the view.

Specifying the view itself is up to the programmer.

There is also a fully featured PaginatedLiveList widget that uses the basic set of widgets but with the view being a ListView.

For usage example see the example directory.

Installation

Add paginated_live_list as a dependency to your project pubspec.yaml and run flutter pub get to install all your dependencies.

# in pubspec.yaml
dependencies:
  paginated_live_list:

Basic Concepts

The Page Cursor

Every page has its own cursor that serves as a pointer for loading the next page. The cursor used to load the first page is null since it has no preceding pages. It's up to the developer to define a concrete PageCursor.

class IntPageCursor implements PageCursor {
  final int nextPage;

  IntPageCursor(this.nextPage);
}

The Page Class

Contains the list of items in the page, a cursor defined by the developer, and a boolean isLastPage to indicate whether or not there is more pages to load.

Usage

Import The Package

import 'package:paginated_live_list/paginated_live_list.dart';

Define Your Items Class

Item class needs to be immutable and implement the equality operator

class Item {
  final String id;
  final DateTime createdAt;

  Item({
    required this.id,
    required this.createdAt,
  });

  @override
  int get hashCode => id.hashCode;

  @override
  bool operator ==(Object? other) {
    if (other is Item && id == other.id) {
      return true;
    } else {
      return false;
    }
  }
}

Implement a PageCursor

You must implement the equality operator.

class CustomPageCursor implements PageCursor {
  final String? lastItemId;

  CustomPageCursor(this.lastItemId);

  @override
  int get hashCode => lastItemId.hashCode;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    } else if (other is! CustomPageCursor) {
      return false;
    }

    return lastItemId == other.lastItemId;
  }
}

Implement a PaginationController

Extend the PaginationController and implement

PaginationController.comparePagesOrder: for Page ordering.

It should simply determine the order of the two pages passed in as parameters using the cursors of the pages or their items.

  @override
  int comparePagesOrder(Page<Item> page, Page<Item> other) {
    // Assuming the pages are in ascending order.
    // for descending order multiply by -1.
    return page.items.last.createdAt.compareTo(other.items.last.createdAt);
  }

PaginationController.updateCursorOfAdjustedPage: for adjustment of page cursors.

Adjustment is needed when a page changes it's cursor due to an update. The adjustment allows the PaginationController to maintain the current pages by not having to reload all the pages after the page that changed it's cursor.

  @override
  Page<Offer> updateCursorOfAdjustedPage(Page<Item> page) {
    final last = page.items.last;
    return Page(
        page.items,
        CustomPageCursor(last.id, last.createdAt),
        page.isLastPage);
  }

PaginationController.onLoadPage: for loading new pages.

This should provide a stream for a page that emits the page then each time the page is updated. All kinds of updates are supported: Updating an item(s), deleting an item(s), and adding a new item(s).

  @override
  Stream<Page<T>> onLoadPage(CustomPageCursor cursor) {
    return dataSource.list(after: cursor.lastItemId, size: 10).map((items) =>
        Page(items, CustomPageCursor(items.lastOrNull?.id), items.length < 10));
  }

Define the View

If you want to have a ListView, you may use the LivePaginatedList widget.

  Widget build(BuildContext context) {
    return PaginationControllerProvider<Offer>(
      create: (context) => CustomPaginationController(),
      child: PaginatedLiveList<Offer>(
        controller: null,
        itemBuilder: (context, state, index) {
          final item = state.items[index];
          return ItemWidget(item: item);
        },
      ),
    );
  }

For other scroll views (e.g. GridView) you need to use the basic widgets. Namely, the PaginationBehavior, the ViewBuilder. See the LivePaginatedList widget implementation for an example.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add paginated_live_list

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  paginated_live_list: ^0.2.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:paginated_live_list/paginated_live_list.dart'; 

example/lib/main.dart

import 'package:example/firebase_options.dart';
import 'package:example/offer_model.dart';
import 'package:example/offers_controller.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:paginated_live_list/paginated_live_list.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Paginated Live List Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PaginationControllerProvider<Offer>(
      create: (context) => OffersController(),
      child: Scaffold(
        floatingActionButton: ActionsButton(),
        body: PaginatedLiveList<Offer>(
          controller: null,
          itemBuilder: (context, state, index) {
            final item = state.items[index];
            return Dismissible(
              key: ValueKey(item.id),
              direction: DismissDirection.endToStart,
              onDismissed: (direction) {
                PaginationControllerProvider.of<Offer, OffersController>(
                        context)!
                    .onDeleteOffer(item);
              },
              background: _buildDismissBackground(Alignment.centerRight),
              child: ListTile(
                leading: _buildAvatar(item.author),
                title: Text('\$${item.price}'),
                subtitle: Text('until ${_formatDate(item.availableUntil)}'),
                trailing: Text('on ${_formatDate(item.createdAt)}'),
              ),
            );
          },
        ),
      ),
    );
  }

  Widget _buildAvatar(String author) {
    return CircleAvatar(
      backgroundColor:
          Colors.primaries[author.hashCode % Colors.primaries.length],
      child: Text(
        author.split(' ').map((name) => name[0].toUpperCase()).join(),
      ),
    );
  }

  Widget _buildDismissBackground(AlignmentGeometry alignment) {
    return Container(
      color: Colors.red,
      child: Align(
        alignment: alignment,
        child: Icon(Icons.delete),
      ),
    );
  }

  String _formatDate(DateTime date) {
    return date.toString().split(" ")[0];
  }
}

class ActionsButton extends StatefulWidget {
  const ActionsButton({
    Key? key,
  }) : super(key: key);

  @override
  State<ActionsButton> createState() => _ActionsButtonState();
}

class _ActionsButtonState extends State<ActionsButton> {
  bool isOpen = false;

  void _toggle() {
    setState(() {
      isOpen = !isOpen;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!isOpen) {
      return FloatingActionButton(
        child: Icon(Icons.arrow_back),
        onPressed: _toggle,
      );
    }

    final separator = const SizedBox(width: 12.0);
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        FloatingActionButton(
          child: Text(
            '+10',
            style: TextStyle(fontSize: 18.0),
          ),
          onPressed: () =>
              PaginationControllerProvider.of<Offer, OffersController>(context)!
                  .onAddOffers(),
        ),
        separator,
        FloatingActionButton(
          child: Icon(Icons.clear_all),
          onPressed: () =>
              PaginationControllerProvider.of<Offer, OffersController>(context)!
                  .onClearOffers(),
        ),
        separator,
        FloatingActionButton(
          child: Icon(Icons.arrow_forward),
          onPressed: _toggle,
        ),
      ],
    );
  }
} 

Download Details:

Author: AkramIzz

Source Code: https://github.com/AkramIzz/paginated_live_list

#flutter #android #ios 

A Paginated Live List in Flutter

A Flutter/Dart Library for Working with Uploadcare REST API

Flutter Uploadcare Client

!!! IMPORTANT !!!

If you are using uploadcare_client@<3.0.0 change the dependency name in your project pubspec.yaml

# from
dependencies:
  uploadcare_client: '2.x.x'

# to
dependencies:
  uploadcare_flutter: ^1.0.0

Limitations

  • It's impossible to use AuthSchemeRegular auth scheme in flutter_web with fetch API because Date request header is forbidden for XMLRequest https://fetch.spec.whatwg.org/#forbidden-header-name.
  • It's impossible to run the upload process in the separate isolate in flutter_web environment.

How to use

Please see this package uploadcare_client for instructions This package was introduced a few extensions for base package (Dimensions, Offsets, FaceRect, FacesEntityExtension), UploadcareImageProvider. If you don't need these features, you can use uploadcare_client directly

Implemented features:

alt flutter uploadcare example

alt flutter uploadcare face rocognition example

alt flutter uploadcare web upload video

alt flutter uploadcare web upload image

Using with widgets

The library provides UploadcareImageProvider for more effective use in the widget ecosystem, how to use image provider:

Image(
  image: UploadcareImageProvider(
    'uploadcare-image-file-uuid',
    // optional, apply transformations to the image
    transformations: [
      BlurTransformation(50),
      GrayscaleTransformation(),
      InvertTransformation(),
      ImageResizeTransformation(Size.square(58))
    ],
    // rest image props...
  ),
)

Face Recognition with flutter

...
final files = ApiFiles(options: options);

final FacesEntity entity = await files.detectFacesWithOriginalImageSize('image-id');
...
RenderBox renderBox = context.findRenderObject();

return FractionallySizedBox(
  widthFactor: 1,
  heightFactor: 1,
  child: Stack(
    children: <Widget>[
      Positioned.fill(
        child: Image(
          image: UploadcareImageProvider(widget.imageId),
          fit: BoxFit.contain,
          alignment: Alignment.topCenter,
        ),
      ),
      ...entity
          .getRelativeFaces(
        Size(
          renderBox.size.width,
          renderBox.size.width /
              entity.originalSize.aspectRatio,
        ),
      )
          .map((face) {
        return Positioned(
          top: face.top,
          left: face.left,
          child: Container(
            width: face.size.width,
            height: face.size.height,
            decoration: BoxDecoration(
              color: Colors.black12,
              border: Border.all(color: Colors.white54, width: 1.0),
            ),
          ),
        );
      }).toList(),
    ],
  ),
);
...

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add uploadcare_flutter

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  uploadcare_flutter: ^4.1.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:uploadcare_flutter/uploadcare_flutter.dart'; 

example/README.md

Example project

Download Details:

Author: KonstantinKai

Source Code: https://github.com/KonstantinKai/uploadcare_client

#flutter #android #ios 

A Flutter/Dart Library for Working with Uploadcare REST API

Dart 2 Hashing Library for OpenSubtitles

opensubtitles_hash

Dart 2 hashing library for OpenSubtitles.

This package is used to calculate OpenSubtitles' hash for video files in order to search for subtitles for that file in OpenSubtitles' database.

More information about the hashing process can be found here: https://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes

Usage

To hash a local file:

// File is the sample AVI file from the HashSourceCodes page
// on OpenSubtitle's Wiki.
String hash = await OpenSubtitlesHasher.computeFileHash(
  new File('breakdance.avi')
);

To hash a remote file (HTTP):

String hash = await OpenSubtitlesHasher.computeURLHash(
  "http://www.opensubtitles.org/addons/avi/breakdance.avi",
  headers: { "X-My-Header": "true" }, // (Optional)
  followRedirects: false              // (Optional)
);

Testing

Simply use pub run test test.dart to test that the library functions correctly.

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add opensubtitles_hash

With Flutter:

 $ flutter pub add opensubtitles_hash

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  opensubtitles_hash: ^2.0.0

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more. 

example/README.md

To hash a local file:

// File is the sample AVI file from the HashSourceCodes page
// on OpenSubtitle's Wiki.
String hash = await OpenSubtitlesHasher.computeFileHash(
  new File('breakdance.avi')
);

To hash a remote file (HTTP):

String hash = await OpenSubtitlesHasher.computeURLHash(
  "http://www.opensubtitles.org/addons/avi/breakdance.avi",
  headers: { "X-My-Header": "true" }, // (Optional)
  followRedirects: false              // (Optional)
); 

Download Details:

Author: NBTX

Source Code: https://github.com/NBTX/opensubtitles_hash

#dart #android #ios 

Dart 2 Hashing Library for OpenSubtitles
Jamel  O'Reilly

Jamel O'Reilly

1660403520

IOSMath: Beautiful Math Equation Rendering on iOS and MacOS

iosMath

iosMath is a library for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written using the LaTeX in a UILabel equivalent class. It uses the same typesetting rules as LaTeX and so the equations are rendered exactly as LaTeX would render them.

It is similar to MathJax or KaTeX for the web but for native iOS or MacOS applications without having to use a UIWebView and Javascript. More importantly, it is significantly faster than using a UIWebView.

Examples

Here are screenshots of some formulae that you could render with this library:
Quadratic Formula

Calculus

AM-GM

Ramanujan Identity

The EXAMPLES.md file contains more examples.

Requirements

iosMath works on iOS 6+ or MacOS 10.8+ and requires ARC to build. It depends on the following Apple frameworks:

  • Foundation.framework
  • CoreGraphics.framework
  • QuartzCore.framework
  • CoreText.framework

Additionally for iOS it requires:

  • UIKit.framework

Additionally for MacOS it requires:

  • AppKit.framework

Installation

Cocoapods

iosMath is available through CocoaPods. To install it:

  1. Add a entry for iosMath to your Podfile: pod 'iosMath'.
  2. Install the pod by running pod install.

Static library

You can also add iosMath as a static library to your project or workspace.

  1. Download the latest code version or add the repository as a git submodule to your git-tracked project.
  2. Open your project in Xcode, then drag and drop iosMath.xcodeproj onto your project or workspace (use the "Product Navigator view").
  3. Select your target and go to the Build phases tab. In the Link Binary With Libraries section select the add button. On the sheet find and add libIosMath.a. You might also need to add iosMath to the Target Dependencies list.
  4. Add the MathFontBundle to the list of Copy Bundle Resources.
  5. Include IosMath wherever you need it with #import <IosMath/IosMath.h>.

Usage

The library provides a class MTMathUILabel which is a UIView that supports rendering math equations. To display an equation simply create an MTMathUILabel as follows:

#import "MTMathUILabel.h"

MTMathUILabel* label = [[MTMathUILabel alloc] init];
label.latex = @"x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}";

Adding MTMathUILabel as a sub-view of your UIView as will render the quadratic formula example shown above.

Included Features

This is a list of formula types that the library currently supports:

  • Simple algebraic equations
  • Fractions and continued fractions
  • Exponents and subscripts
  • Trigonometric formulae
  • Square roots and n-th roots
  • Calculus symbos - limits, derivatives, integrals
  • Big operators (e.g. product, sum)
  • Big delimiters (using \left and \right)
  • Greek alphabet
  • Combinatorics (\binom, \choose etc.)
  • Geometry symbols (e.g. angle, congruence etc.)
  • Ratios, proportions, percents
  • Math spacing
  • Overline and underline
  • Math accents
  • Matrices
  • Equation alignment
  • Change bold, roman, caligraphic and other font styles (\bf, \text, etc.)
  • Most commonly used math symbols
  • Colors

Example

There is a sample app included in this project that shows how to use the app and the different equations that you can render. To run the sample app, clone the repository, and run pod install first. Then on iOS run the iosMathExample app. For MacOS run the MacOSMath app.

Advanced configuration

MTMathUILabel supports some advanced configuration options:

Math mode

You can change the mode of the MTMathUILabel between Display Mode (equivalent to $$ or \[ in LaTeX) and Text Mode (equivalent to $ or \( in LaTeX). The default style is Display. To switch to Text simply:

label.labelMode = kMTMathUILabelModeText;

Text Alignment

The default alignment of the equations is left. This can be changed to center or right as follows:

label.textAlignment = kMTTextAlignmentCenter;

Font size

The default font-size is 20pt. You can change it as follows:

label.fontSize = 30;

Font

The default font is Latin Modern Math. This can be changed as:

label.font = [[MTFontManager fontManager] termesFontWithSize:20];

This project has 3 fonts bundled with it, but you can use any OTF math font.

Color

The default color of the rendered equation is black. You can change it to any other color as follows:

label.textColor = [UIColor redColor];

It is also possible to set different colors for different parts of the equation. Just access the displayList field and set the textColor on the underlying displays that you want to change the color of.

Custom Commands

You can define your own commands that are not already predefined. This is similar to macros is LaTeX. To define your own command use:

[MTMathAtomFactory addLatexSymbol:@"lcm"
                            value:[MTMathAtomFactory operatorWithName:@"lcm" limits:NO]];

This creates a \lcm command that can be used in the LaTeX.

Content Insets

The MTMathUILabel has contentInsets for finer control of placement of the equation in relation to the view.

If you need to set it you can do as follows:

label.contentInsets = UIEdgeInsetsMake(0, 10, 0, 20);

Error handling

If the LaTeX text given to MTMathUILabel is invalid or if it contains commands that aren't currently supported then an error message will be displayed instead of the label.

This error can be programmatically retrieved as label.error. If you prefer not to display anything then set:

label.displayErrorInline = NO;

Future Enhancements

Note this is not a complete implementation of LaTeX math mode. There are some important pieces that are missing and will be included in future updates. This includes:

  • Support for explicit big delimiters (bigl, bigr etc.)
  • Addition of missing plain TeX commands

Related Projects

For people looking for things beyond just rendering math, there are two related projects:

  • MathEditor: A WYSIWYG editor for math equations on iOS.
  • MathSolver: A library for solving math equations.

Author: kostub
Source Code: https://github.com/kostub/iosMath
License: MIT license

#ios #objective-c #math 

IOSMath: Beautiful Math Equation Rendering on iOS and MacOS

Blossom Tab Manger Package for Flutter Blossom and Related Projects

Tabs manager for flutter-blossom project.

Features

  • You can drag and drop tabs and reorder them.
  • dynamically add tabs at runtime.
  • save the current state of tab manger for later use (i.e- at next app restart).
  • customize appearance and behavior.

Getting started

first add it to your project

blossom_tabs: ^4.0.0

then import it

import 'package:blossom_tabs/blossom_tabs.dart';

Usage

You can add in widget tree like this -

// configure `controller`
var _controller = BlossomTabController<int>(tabs: []); // infer data type for easy access

return BlossomTabControllerScope<int>(
  controller: _controller,
  child: Scaffold(
    appBar: BlossomTabBar<int>(
      height: 48,
      selectedColor: Colors.blue,
      stickyColor: Colors.white,
      backgroundColor: Colors.blue.withOpacity(0.3),
      dividerColor: Colors.blue,
      tabBuilder: (context, tab, isActive) => Text(e.id),
    ),
    body: BlossomTabView<int>(
      builder: (tab) => Text(tab.id)
    ),
  ),
);

Additional information

Additionally you can listen to tabs state changes using BlossomTabControllerScopeDescendant. like this -

BlossomTabControllerScopeDescendant<int>(
  builder: (context, controller) {
  return Container(
    color: controller.currentTab == 'd' ? Colors.white : Colors.blue,
  );
});

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add blossom_tabs

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  blossom_tabs: ^4.0.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:blossom_tabs/blossom_tabs.dart'; 

example/lib/main.dart

import 'dart:convert';
import 'dart:math';

import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:blossom_tabs/blossom_tabs.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
  doWhenWindowReady(() {
    const initialSize = Size(800, 450);
    appWindow.minSize = initialSize;
    appWindow.size = initialSize;
    appWindow.alignment = Alignment.center;
    appWindow.show();
  });
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

Widget buildTab(
  BuildContext context, {
  required bool isActive,
  bool useRow = true,
  Widget? icon,
  Widget? activeIcon,
  String? title,
  TextStyle? style,
  TextStyle? activeStyle,
}) {
  var children = [
    (isActive ? activeIcon ?? icon : icon) ??
        const SizedBox(
          width: 10,
        ),
    if (title != null)
      Flexible(
        child: Text(
          title,
          softWrap: false,
          overflow: TextOverflow.fade,
          style: isActive ? activeStyle ?? style : style,
        ),
      ),
  ];
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 4.0),
    child: useRow
        ? Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Row(children: children),
            ],
          )
        : Row(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: children,
              ),
            ],
          ),
  );
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _controller = BlossomTabController<int>(tabs: []);
  var _tabs = <BlossomTab<int>>[];

  BlossomTab<int> _getTab(String e) => BlossomTab<int>(
        id: e,
        data: int.parse(e.codeUnits.join()),
        title: e.toUpperCase(),
        isSticky: e == 'd',
      );

  @override
  void initState() {
    _tabs = ['a', 'b', 'c', 'd', 'e']
        .map(
          (e) => _getTab(e),
          //     BlossomTab.fromJson<int>(
          //   {
          //     "id": e,
          //     "data": {"value": int.parse(e.codeUnits.join())},
          //     "title": e.toUpperCase(),
          //     "isSticky": e == 'd' ? true : false,
          //     "maxWidth": 200.0,
          //     "stickyWidth": 50.0
          //   },
          //   (map) => map['value'],
          // ),
        )
        .toList();
    _controller = BlossomTabController<int>(currentTab: 'b', tabs: _tabs);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return BlossomTabControllerScope(
      controller: _controller,
      child: Scaffold(
        appBar: PreferredSize(
          preferredSize: const Size.fromHeight(85),
          child: Stack(
            children: [
              MoveWindow(
                child: BlossomTabBar<int>(
                  height: 85,
                  bottomBarHeight: 40,
                  selectedColor: Colors.blue,
                  dragColor: Colors.blue.withOpacity(0.6),
                  stickyColor: Colors.white,
                  backgroundColor: Colors.blue.withOpacity(0.3),
                  dividerColor: Colors.blue,
                  bottomColor: Colors.blue,
                  margin: const EdgeInsets.only(left: 4, top: 4, right: 140),
                  tabBarMargin: 4,
                  tabBuilder: (context, tab, isActive) => buildTab(
                    context,
                    isActive: isActive,
                    title: tab.id,
                    activeStyle:
                        tab.id == 'd' ? null : const TextStyle(color: Colors.white),
                    icon: tab.id == 'd'
                        ? null
                        : const Padding(
                            padding: EdgeInsets.all(4.0),
                            child: Icon(
                              Icons.ac_unit,
                              size: 14,
                              color: Colors.black26,
                            ),
                          ),
                    activeIcon: tab.id == 'd'
                        ? null
                        : const Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Icon(
                              Icons.ac_unit,
                              size: 14,
                              color: Colors.white,
                            ),
                          ),
                  ),
                  tabActions: (context, tab) => [
                    if (tab.id != 'd')
                      Listener(
                        onPointerDown: (_) {
                          _controller.removeTabById(tab.id);
                        },
                        child: const Padding(
                          padding: EdgeInsets.all(4.0),
                          child: Icon(
                            Icons.close,
                            size: 14,
                            color: Colors.white,
                          ),
                        ),
                      ),
                  ],
                  bottomBar: BlossomTabControllerScopeDescendant<int>(
                      builder: (context, controller) {
                    // Future.delayed(Duration.zero)
                    //     .then((_) => print(jsonEncode(controller.toJson())));
                    return Container(
                      color: controller.currentTab == 'd' ? Colors.white : null,
                    );
                  }),
                  actions: [
                    Padding(
                      padding: const EdgeInsets.only(left: 6.0),
                      child: NewTabBtn(
                        onTap: () {
                          final z = _controller.tabs.map((e) => e.id).toList()..sort();
                          var c = z.isEmpty ? 'a' : z.last;
                          final lastCharacter =
                              String.fromCharCode(c.codeUnitAt(c.length - 1) + 1);
                          c = c.substring(0, c.length - 1) + lastCharacter;
                          _controller.addTab(_getTab(c));
                        },
                      ),
                    )
                  ],
                ),
              ),
              Positioned(
                right: 0,
                child: WindowButtons(),
              ),
            ],
          ),
        ),
        body: Row(
          children: [
            BlossomVerticalTabBar<int>(
              width: 240,
              sideBarWidth: 180,
              selectedColor: Colors.blue,
              dragColor: Colors.blue.withOpacity(0.6),
              stickyColor: Colors.white,
              backgroundColor: Colors.blue.withOpacity(0.3),
              dividerColor: Colors.blue,
              sideBarColor: Colors.blue,
              margin: const EdgeInsets.only(left: 0, top: 0, right: 0, bottom: 40),
              tabBarMargin: 0,
              showIndicator: true,
              indicatorColor: Colors.white,
              tabBuilder: (context, tab, isActive) => buildTab(
                context,
                isActive: isActive,
                useRow: false,
                title: tab.id,
                activeStyle: tab.id == 'd' ? null : const TextStyle(color: Colors.white),
                activeIcon: tab.id == 'd'
                    ? null
                    : const Padding(
                        padding: EdgeInsets.all(8.0),
                        child: Icon(
                          Icons.ac_unit,
                          size: 14,
                          color: Colors.white,
                        ),
                      ),
              ),
              sideBar: BlossomTabControllerScopeDescendant<int>(
                  builder: (context, controller) {
                // Future.delayed(Duration.zero)
                //     .then((_) => print(jsonEncode(controller.toJson())));
                return Container(
                  color: controller.currentTab == 'd' ? Colors.white : null,
                );
              }),
              actions: [
                Padding(
                  padding: const EdgeInsets.only(top: 6.0),
                  child: NewTabBtn(
                    onTap: () {
                      final z = _controller.tabs.map((e) => e.id).toList()..sort();
                      var c = z.isEmpty ? 'a' : z.last;
                      final lastCharacter =
                          String.fromCharCode(c.codeUnitAt(c.length - 1) + 1);
                      c = c.substring(0, c.length - 1) + lastCharacter;
                      _controller.addTab(_getTab(c));
                    },
                  ),
                )
              ],
            ),
            Expanded(
              child: BlossomTabView<int>(
                builder: (tab) => ColorBox(
                  child: Center(child: ColorBox(child: Center(child: Text(tab.id)))),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class NewTabBtn extends StatefulWidget {
  const NewTabBtn({
    Key? key,
    required this.onTap,
  }) : super(key: key);
  final void Function() onTap;

  @override
  State<NewTabBtn> createState() => _NewTabBtnState();
}

class _NewTabBtnState extends State<NewTabBtn> {
  var _opacity = 0.1;
  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: widget.onTap,
      onHover: (h) => setState(() => _opacity = h ? 0.3 : 0.1),
      child: Container(
        padding: const EdgeInsets.all(2.0),
        color: Colors.blue.withOpacity(_opacity),
        child: const Icon(
          Icons.add,
          size: 22,
          color: Colors.white,
        ),
      ),
    );
  }
}

class ColorBox extends StatefulWidget {
  const ColorBox({Key? key, this.child}) : super(key: key);

  final Widget? child;

  @override
  _ColorBoxState createState() => _ColorBoxState();
}

class _ColorBoxState extends State<ColorBox> {
  Color? _color;

  _randomColor() => Color(0xFF000000 + Random().nextInt(0x00FFFFFF));

  @override
  void initState() {
    _color = _randomColor();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _color = _randomColor();
        });
      },
      child: Container(width: 150, height: 150, color: _color, child: widget.child),
    );
  }
}

final buttonColors = WindowButtonColors(
  iconNormal: Colors.blue,
  mouseOver: Colors.blue.withOpacity(0.2),
  mouseDown: Colors.blue,
);

final closeButtonColors = WindowButtonColors(
  mouseOver: Colors.red.withOpacity(0.9),
  mouseDown: Colors.red,
  iconNormal: Colors.blue,
);

class WindowButtons extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        MinimizeWindowButton(colors: buttonColors),
        MaximizeWindowButton(colors: buttonColors),
        CloseWindowButton(colors: closeButtonColors),
      ],
    );
  }
} 

Download Details:

Author: flutter-blossom

Source Code: https://github.com/flutter-blossom/blossom_tabs
#flutter #android #ios 

Blossom Tab Manger Package for Flutter Blossom and Related Projects

Dart Module for Communicating with The Veryfi OCR API

Dart module for communicating with the Veryfi OCR API

Installation

Install from https://pub.dev/packages/veryfi_dart

Getting Started

Obtaining Client ID and user keys

If you don't have an account with Veryfi, please go ahead and register here: https://hub.veryfi.com/signup/api/

Veryfi Dart Client Library

The veryfi library can be used to communicate with Veryfi API. All available functionality is described here: https://veryfi.github.io/veryfi-dart/

Below is the sample Dart code using veryfi to OCR and extract data from a document:

Import package:

import 'package:veryfi_dart/veryfi_dart.dart';

Process a document from file

Future<void> processDocument() async {
    String fileName = 'receipt.jpg';
    File file = File(fileName);
    Uint8List imageData = file.readAsBytesSync();
    String fileData = base64Encode(imageData);
    VeryfiDart client = VeryfiDart(
        'yourClientId', 'yourClientSecret', 'yourUsername', 'yourApiKey');

    await client.processDocument(fileName, fileData).then(
      (response) {
        print('success');
      },
    ).catchError((error) {
      print('error');
    });
}

Update a document

Future<void> updateDocument() async {
    VeryfiDart client = VeryfiDart(
        'yourClientId', 'yourClientSecret', 'yourUsername', 'yourApiKey');
    final Map<String, dynamic> params = {'notes': 'Test'};
    await client.updateDocument('123', params).then(
      (response) {
        print('success');
      },
    ).catchError((error) {
      print('error');
    });
}

Release

  1. Create new branch for your code
  2. Change version in constants.dart and pubspec.yaml with the same version.
  3. Commit changes and push to Github
  4. Create PR pointing to master branch and add a Veryfi member as a reviewer
  5. Tag your commit with the new version
  6. The new version will be accesible through Pub Dev.

Need help?

If you run into any issue or need help installing or using the library, please contact support@veryfi.com.

If you found a bug in this library or would like new features added, then open an issue or pull requests against this repo!

To learn more about Veryfi visit https://www.veryfi.com/

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add veryfi_dart

With Flutter:

 $ flutter pub add veryfi_dart

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  veryfi_dart: ^0.0.4

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:veryfi_dart/veryfi_dart.dart'; 

example/veryfi_dart_example.dart

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:veryfi_dart/veryfi_dart.dart';

Future<void> main() async {
  await processDocument();
  await updateDocument();
}

Future<void> processDocument() async {
  String fileName = 'filename.jpg';
  File file = File(fileName);
  Uint8List imageData = file.readAsBytesSync();
  String fileData = base64Encode(imageData);
  VeryfiDart client = VeryfiDart(
      'yourClientId', 'yourClientSecret', 'yourUsername', 'yourApiKey');

  await client.processDocument(fileName, fileData).then(
    (response) {
      print('success');
    },
  ).catchError((error) {
    print('error');
  });
}

Future<void> updateDocument() async {
  VeryfiDart client = VeryfiDart(
      'yourClientId', 'yourClientSecret', 'yourUsername', 'yourApiKey');
  final Map<String, dynamic> params = {'notes': 'Test'};
  await client.updateDocument('123', params).then(
    (response) {
      print('success');
    },
  ).catchError((error) {
    print('error');
  });
} 

Download Details:

Author: 

Source Code: https://pub.dev/packages/veryfi_dart/

#dart #android #ios 

Dart Module for Communicating with The Veryfi OCR API

Backdrop Implementation in Flutter

backdrop

Backdrop implementation in flutter.

This widget is in active development. Any contribution, idea, criticism or feedback is welcomed.

Quick links

  
Packagehttps://pub.dev/packages/backdrop
API Docshttps://pub.dev/documentation/backdrop/latest/backdrop/backdrop-library.html
Live Demohttps://fluttercommunity.github.io/backdrop/demo/#/
Git Repohttps://github.com/fluttercommunity/backdrop
Issue Trackerhttps://github.com/fluttercommunity/backdrop/issues
Chat Roomhttps://gitter.im/flutter-backdrop

Usage

BackdropScaffold

Use BackdropScaffold instead of the standard Scaffold in your app. A backLayer and a frontLayer have to be defined for the backdrop to work.

BackdropScaffold(
  appBar: BackdropAppBar(
    title: Text("Backdrop Example"),
    actions: <Widget>[
      BackdropToggleButton(
        icon: AnimatedIcons.list_view,
      )
    ],
  ),
  backLayer: Center(
    child: Text("Back Layer"),
  ),
  frontLayer: Center(
    child: Text("Front Layer"),
  ),
)

BackdropScaffold example

Navigation with backdrop

To use backdrop for navigation, use the provided BackdropNavigationBackLayer as backLayer.

The BackdropNavigationBackLayer contains a property items representing the list elements shown on the back layer. The front layer has to be "manually" set depending on the current index, which can be accessed with the onTap callback.

int _currentIndex = 0;
final List<Widget> _pages = [Widget1(), Widget2()];

@override
Widget build(BuildContext context) {
  return MaterialApp(
    title: 'Backdrop Demo',
    home: BackdropScaffold(
      appBar: BackdropAppBar(
        title: Text("Navigation Example"),
        actions: <Widget>[
          BackdropToggleButton(
            icon: AnimatedIcons.list_view,
          )
        ],
      ),
      stickyFrontLayer: true,
      frontLayer: _pages[_currentIndex],
      backLayer: BackdropNavigationBackLayer(
        items: [
          ListTile(title: Text("Widget 1")),
          ListTile(title: Text("Widget 2")),
        ],
        onTap: (int position) => {setState(() => _currentIndex = position)},
      ),
    ),
  );
}

BackdropNavigationScaffold example

Accessing underlying backdrop functionalities

To access backdrop related functionalities, use Backdrop.of(context) to get underlying BackdropScaffoldState.

BackdropScaffoldState exposes various properties and methods like:

  • properties
    • animationController -> AnimationController
    • scaffoldKey -> GlobalKey<ScaffoldState>
    • isBackLayerConcealed -> bool
    • isBackLayerRevealed -> bool
  • methods
    • fling()
    • concealBackLayer()
    • revealBackLayer()

Note: Backdrop is an InheritedWidget and therefore like Scaffold.of, Theme.of and MediaQuery.of, the BuildContext context passed to Backdrop.of(context) should be of a Widget that is under the BackdropScaffold in the "widget tree". In other words, Backdrop.of called inside a widget where the BackdropScaffold is initalized will not work explicitly, since the context passed is of the widget that will build BackdropScaffold therefore above BackdropScaffold. This can be solved by either making a seperate Widget where Backdrop.of needs to be used and make it the "child" of BackdropScaffold or wrap the Backdrop.of usage around Builder widget so that the "correct" context (from Builder) is passed to Backdrop.of. This answere on SO and FWotW video on Builder gives a very good idea of how and why Builder works in later case.

For more information, check out sample code in the example directory, demo app with use-cases and code for it and API references generated by pub.dev.

Contribute

Check proposal documents for v1.0 and web&desktop milestones before you begin with any contibution.

  1. You'll need a GitHub account.
  2. Fork the repository.
  3. Pick an issue to work on from issue tracker.
  4. Implement it.
  5. Send merge request.
  6. Star this project.
  7. Become a hero!!

Features and bugs

Please file feature requests and bugs at the issue tracker.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add backdrop

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  backdrop: ^0.8.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:backdrop/backdrop.dart'; 

example/lib/main.dart

/*
*
* Check live demo with various use-cases and its code on https://fluttercommunity.github.io/backdrop/demo/#/
*
* */

import 'package:backdrop/backdrop.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// Example app for demoing [BackdropScaffold]
class MyApp extends StatelessWidget {
  /// Default constructor for [MyApp].
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Backdrop Demo',
      home: BackdropScaffold(
        appBar: BackdropAppBar(
          title: const Text("Backdrop Example"),
          actions: const <Widget>[
            BackdropToggleButton(
              icon: AnimatedIcons.list_view,
            )
          ],
        ),
        backLayer: const Center(
          child: Text("Back Layer"),
        ),
        subHeader: const BackdropSubHeader(
          title: Text("Sub Header"),
        ),
        frontLayer: const Center(
          child: Text("Front Layer"),
        ),
      ),
    );
  }
} 

Download Details:

Author: fluttercommunity

Source Code: https://github.com/fluttercommunity/backdrop

#flutter #android #ios 

Backdrop Implementation in Flutter