Commit 6d0fde91 by whitepixelstudios Committed by GitHub

Merge pull request #1 from CosmicMind/master

Update to match CosmicMind version
parents 461dcd69 6bfd58cf
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'Material' s.name = 'Material'
s.version = '2.5.2' s.version = '2.6.3'
s.license = 'BSD-3-Clause' s.license = 'BSD-3-Clause'
s.summary = 'An animation and graphics framework for Material Design in Swift.' s.summary = 'An animation and graphics framework for Material Design in Swift.'
s.homepage = 'http://materialswift.com' s.homepage = 'http://materialswift.com'
......
...@@ -18,8 +18,6 @@ ...@@ -18,8 +18,6 @@
9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0D1DBE6AF600DA84DB /* Capture.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0D1DBE6AF600DA84DB /* Capture.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0E1DBE6AF600DA84DB /* CaptureController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0E1DBE6AF600DA84DB /* CaptureController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0841DFCA8CF00410F8F /* Editor.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961DED451DCC40C500F425B6 /* Editor.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0851DFCA8CF00410F8F /* EditorController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961DED4A1DCC546100F425B6 /* EditorController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CB9A1DAD3D1D003E2611 /* HeightPreset.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CB9A1DAD3D1D003E2611 /* HeightPreset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0871DFCA8CF00410F8F /* PageTabBarController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 963FBF071D669D14008F8512 /* PageTabBarController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0871DFCA8CF00410F8F /* PageTabBarController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 963FBF071D669D14008F8512 /* PageTabBarController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */; settings = {ATTRIBUTES = (Public, ); }; };
...@@ -117,8 +115,6 @@ ...@@ -117,8 +115,6 @@
965E81211DD4D5C800D61E4B /* TextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79D1CB40DC500C806FE /* TextStorage.swift */; }; 965E81211DD4D5C800D61E4B /* TextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79D1CB40DC500C806FE /* TextStorage.swift */; };
965E81221DD4D5C800D61E4B /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79E1CB40DC500C806FE /* TextView.swift */; }; 965E81221DD4D5C800D61E4B /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79E1CB40DC500C806FE /* TextView.swift */; };
965E81231DD4D7C800D61E4B /* BottomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7591CB40DC500C806FE /* BottomTabBar.swift */; }; 965E81231DD4D7C800D61E4B /* BottomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7591CB40DC500C806FE /* BottomTabBar.swift */; };
965E81241DD4D7C800D61E4B /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961DED451DCC40C500F425B6 /* Editor.swift */; };
965E81251DD4D7C800D61E4B /* EditorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961DED4A1DCC546100F425B6 /* EditorController.swift */; };
965E81261DD4D7C800D61E4B /* CharacterAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961276621DCD8B1800A7D920 /* CharacterAttribute.swift */; }; 965E81261DD4D7C800D61E4B /* CharacterAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961276621DCD8B1800A7D920 /* CharacterAttribute.swift */; };
9697F7BF1D8F2572004741EC /* Divider.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96230AB71D6A520C00AF47DC /* Divider.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9697F7BF1D8F2572004741EC /* Divider.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96230AB71D6A520C00AF47DC /* Divider.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9697F7C01D8F2572004741EC /* Material+CALayer.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96F1DC871D654FDF0025F925 /* Material+CALayer.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 9697F7C01D8F2572004741EC /* Material+CALayer.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96F1DC871D654FDF0025F925 /* Material+CALayer.swift */; settings = {ATTRIBUTES = (Public, ); }; };
...@@ -195,8 +191,6 @@ ...@@ -195,8 +191,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; }; 961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; };
961730591E145DE900A9A297 /* CollectionViewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCard.swift; sourceTree = "<group>"; }; 961730591E145DE900A9A297 /* CollectionViewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCard.swift; sourceTree = "<group>"; };
961DED451DCC40C500F425B6 /* Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = "<group>"; };
961DED4A1DCC546100F425B6 /* EditorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorController.swift; sourceTree = "<group>"; };
961E6BDE1DDA2A95004E6C93 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; }; 961E6BDE1DDA2A95004E6C93 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
961E6BE11DDA2AF3004E6C93 /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; }; 961E6BE11DDA2AF3004E6C93 /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
961EFC571D738FF600E84652 /* SnackbarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnackbarController.swift; sourceTree = "<group>"; }; 961EFC571D738FF600E84652 /* SnackbarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnackbarController.swift; sourceTree = "<group>"; };
...@@ -311,15 +305,6 @@ ...@@ -311,15 +305,6 @@
name = TextField; name = TextField;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
961DED441DCC40AC00F425B6 /* Editor */ = {
isa = PBXGroup;
children = (
961DED451DCC40C500F425B6 /* Editor.swift */,
961DED4A1DCC546100F425B6 /* EditorController.swift */,
);
name = Editor;
sourceTree = "<group>";
};
961E6BDD1DDA2A7E004E6C93 /* Application */ = { 961E6BDD1DDA2A7E004E6C93 /* Application */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -377,12 +362,12 @@ ...@@ -377,12 +362,12 @@
name = Height; name = Height;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
962DDD071D6FBBB7001C307C /* Page */ = { 962DDD071D6FBBB7001C307C /* PageTabBar */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
963FBF071D669D14008F8512 /* PageTabBarController.swift */, 963FBF071D669D14008F8512 /* PageTabBarController.swift */,
); );
name = Page; name = PageTabBar;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
962DDD081D6FBBD0001C307C /* BottomTabBar */ = { 962DDD081D6FBBD0001C307C /* BottomTabBar */ = {
...@@ -519,6 +504,7 @@ ...@@ -519,6 +504,7 @@
96BCB7571CB40DC500C806FE /* iOS */ = { 96BCB7571CB40DC500C806FE /* iOS */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
96EF418E1E835E850012CA1C /* Animation */,
961E6BDD1DDA2A7E004E6C93 /* Application */, 961E6BDD1DDA2A7E004E6C93 /* Application */,
96264BE41D833C8400576F37 /* Bar */, 96264BE41D833C8400576F37 /* Bar */,
962DDD081D6FBBD0001C307C /* BottomTabBar */, 962DDD081D6FBBD0001C307C /* BottomTabBar */,
...@@ -530,7 +516,6 @@ ...@@ -530,7 +516,6 @@
96328B9A1E05C135009A4C90 /* Data */, 96328B9A1E05C135009A4C90 /* Data */,
96BCB80B1CB410CC00C806FE /* Device */, 96BCB80B1CB410CC00C806FE /* Device */,
96230AB61D6A51FD00AF47DC /* Divider */, 96230AB61D6A51FD00AF47DC /* Divider */,
961DED441DCC40AC00F425B6 /* Editor */,
96BCB80A1CB410A100C806FE /* Extension */, 96BCB80A1CB410A100C806FE /* Extension */,
96BCB8071CB4101C00C806FE /* Font */, 96BCB8071CB4101C00C806FE /* Font */,
9602F00C1DA1163000F3FB79 /* Grid */, 9602F00C1DA1163000F3FB79 /* Grid */,
...@@ -542,7 +527,7 @@ ...@@ -542,7 +527,7 @@
96BCB8091CB4107700C806FE /* Motion */, 96BCB8091CB4107700C806FE /* Motion */,
96BCB8011CB40F1700C806FE /* Navigation */, 96BCB8011CB40F1700C806FE /* Navigation */,
961E6BEF1DDA4B04004E6C93 /* NavigationDrawer */, 961E6BEF1DDA4B04004E6C93 /* NavigationDrawer */,
962DDD071D6FBBB7001C307C /* Page */, 962DDD071D6FBBB7001C307C /* PageTabBar */,
96717B151DBE6B1800DA84DB /* Photos */, 96717B151DBE6B1800DA84DB /* Photos */,
96328B8F1E05B69A009A4C90 /* Reminders */, 96328B8F1E05B69A009A4C90 /* Reminders */,
9626CA951DAB5370003E2611 /* Root */, 9626CA951DAB5370003E2611 /* Root */,
...@@ -681,8 +666,6 @@ ...@@ -681,8 +666,6 @@
children = ( children = (
96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */, 96BFC1671E61D9FD0075DE1F /* Material+Motion.swift */,
96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */, 96BFC1691E61DAA10075DE1F /* Material+MotionAnimation.swift */,
96BCB7821CB40DC500C806FE /* PulseAnimation.swift */,
965532281E47E388005C2792 /* SpringAnimation.swift */,
); );
name = Motion; name = Motion;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -750,6 +733,15 @@ ...@@ -750,6 +733,15 @@
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
96EF418E1E835E850012CA1C /* Animation */ = {
isa = PBXGroup;
children = (
96BCB7821CB40DC500C806FE /* PulseAnimation.swift */,
965532281E47E388005C2792 /* SpringAnimation.swift */,
);
name = Animation;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */ /* Begin PBXHeadersBuildPhase section */
...@@ -821,8 +813,6 @@ ...@@ -821,8 +813,6 @@
9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */, 9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */,
9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */, 9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */,
9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */, 9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */,
9617B0841DFCA8CF00410F8F /* Editor.swift in Headers */,
9617B0851DFCA8CF00410F8F /* EditorController.swift in Headers */,
9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */, 9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */,
9617B0871DFCA8CF00410F8F /* PageTabBarController.swift in Headers */, 9617B0871DFCA8CF00410F8F /* PageTabBarController.swift in Headers */,
9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */, 9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */,
...@@ -928,8 +918,6 @@ ...@@ -928,8 +918,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
965E81231DD4D7C800D61E4B /* BottomTabBar.swift in Sources */, 965E81231DD4D7C800D61E4B /* BottomTabBar.swift in Sources */,
965E81241DD4D7C800D61E4B /* Editor.swift in Sources */,
965E81251DD4D7C800D61E4B /* EditorController.swift in Sources */,
961E6BE21DDA2AF3004E6C93 /* Screen.swift in Sources */, 961E6BE21DDA2AF3004E6C93 /* Screen.swift in Sources */,
965E81261DD4D7C800D61E4B /* CharacterAttribute.swift in Sources */, 965E81261DD4D7C800D61E4B /* CharacterAttribute.swift in Sources */,
965E80FF1DD4D5C800D61E4B /* BottomNavigationController.swift in Sources */, 965E80FF1DD4D5C800D61E4B /* BottomNavigationController.swift in Sources */,
......
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0820" LastUpgradeVersion = "0830"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.5.2</string> <string>2.6.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -137,7 +137,6 @@ open class Button: UIButton, Pulseable, PulseableLayer { ...@@ -137,7 +137,6 @@ open class Button: UIButton, Pulseable, PulseableLayer {
*/ */
public required init?(coder aDecoder: NSCoder) { public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) super.init(coder: aDecoder)
tintColor = Color.blue.base
prepare() prepare()
} }
......
...@@ -198,7 +198,7 @@ open class Card: PulseView { ...@@ -198,7 +198,7 @@ open class Card: PulseView {
} }
container.height = h container.height = h
height = h bounds.size.height = h
} }
/** /**
......
...@@ -117,7 +117,7 @@ extension NSMutableAttributedString { ...@@ -117,7 +117,7 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values. - Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func addAttributes(characterAttributes: [CharacterAttribute : Any] = [:], range: NSRange) { open func addAttributes(characterAttributes: [CharacterAttribute: Any], range: NSRange) {
for (k, v) in characterAttributes { for (k, v) in characterAttributes {
addAttribute(characterAttribute: k, value: v, range: range) addAttribute(characterAttribute: k, value: v, range: range)
} }
...@@ -139,7 +139,7 @@ extension NSMutableAttributedString { ...@@ -139,7 +139,7 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values. - Parameter characterAttributes: A Dictionary of CharacterAttribute type keys and Any type values.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func updateAttributes(characterAttributes: [CharacterAttribute : Any] = [:], range: NSRange) { open func updateAttributes(characterAttributes: [CharacterAttribute: Any], range: NSRange) {
for (k, v) in characterAttributes { for (k, v) in characterAttributes {
updateAttribute(characterAttribute: k, value: v, range: range) updateAttribute(characterAttribute: k, value: v, range: range)
} }
...@@ -159,7 +159,7 @@ extension NSMutableAttributedString { ...@@ -159,7 +159,7 @@ extension NSMutableAttributedString {
- Parameter characterAttributes: An Array of CharacterAttributes. - Parameter characterAttributes: An Array of CharacterAttributes.
- Parameter range: A NSRange. - Parameter range: A NSRange.
*/ */
open func removeAttributes(characterAttributes: [CharacterAttribute] = [], range: NSRange) { open func removeAttributes(characterAttributes: [CharacterAttribute], range: NSRange) {
for k in characterAttributes { for k in characterAttributes {
removeAttribute(characterAttribute: k, range: range) removeAttribute(characterAttribute: k, range: range)
} }
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(EditorDelegate)
public protocol EditorDelegate {
/**
A delegation method that is executed when text will be
processed during editing.
- Parameter editor: An Editor.
- Parameter willProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func editor(editor: Editor, willProcessEditing textStorage: TextStorage, text: String, range: NSRange)
/**
A delegation method that is executed when text has been
processed after editing.
- Parameter editor: An Editor.
- Parameter didProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func editor(editor: Editor, didProcessEditing textStorage: TextStorage, text: String, range: NSRange)
/**
A delegation method that is executed when the textView should begin editing.
- Parameter editor: An Editor.
- Parameter shouldBeginEditing textView: A UITextView.
- Returns: A boolean indicating if the textView should begin editing, true if
yes, false otherwise.
*/
@objc
optional func editor(editor: Editor, shouldBeginEditing textView: UITextView) -> Bool
/**
A delegation method that is executed when the textView should end editing.
- Parameter editor: An Editor.
- Parameter shouldEndEditing textView: A UITextView.
- Returns: A boolean indicating if the textView should end editing, true if
yes, false otherwise.
*/
@objc
optional func editor(editor: Editor, shouldEndEditing textView: UITextView) -> Bool
/**
A delegation method that is executed when the textView did begin editing.
- Parameter editor: An Editor.
- Parameter didBeginEditing textView: A UITextView.
*/
@objc
optional func editor(editor: Editor, didBeginEditing textView: UITextView)
/**
A delegation method that is executed when the textView did begin editing.
- Parameter editor: An Editor.
- Parameter didBeginEditing textView: A UITextView.
*/
@objc
optional func editor(editor: Editor, didEndEditing textView: UITextView)
/**
A delegation method that is executed when the textView should change text in
a given range with replacement text.
- Parameter editor: An Editor.
- Parameter textView: A UITextView.
- Parameter shouldChangeTextIn range: A NSRange.
- Parameter replacementText text: A String.
- Returns: A boolean indicating if the textView should change text in a given
range with the given replacement text, true if yes, false otherwise.
*/
@objc
optional func editor(editor: Editor, textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
/**
A delegation method that is executed when the textView did change.
- Parameter editor: An Editor.
- Parameter didChange textView: A UITextView.
*/
@objc
optional func editor(editor: Editor, didChange textView: UITextView)
/**
A delegation method that is executed when the textView did change a selection.
- Parameter editor: An Editor.
- Parameter didChangeSelection textView: A UITextView.
*/
@objc
optional func editor(editor: Editor, didChangeSelection textView: UITextView)
/**
A delegation method that is executed when the textView should interact with
a URL in a given character range.
- Parameter editor: An Editor.
- Parameter textView: A UITextView.
- Parameter shouldInteractWith URL: A URL.
- Parameter in characterRange: A Range.
- Returns: A boolean indicating if the textView should interact with a URL in
a given character range, true if yes, false otherwise.
@available(iOS, introduced: 8.0, deprecated: 10.0)
@objc
optional func editor(editor: Editor, textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool
A delegation method that is executed when the textView should interact with
a text attachment in a given character range.
- Parameter editor: An Editor.
- Parameter textView: A UITextView.
- Parameter shouldInteractWith textAttachment: A NSTextAttachment.
- Parameter in characterRange: A Range.
- Returns: A boolean indicating if the textView should interact with a
NSTextAttachment in a given character range, true if yes, false otherwise.
@available(iOS, introduced: 8.0, deprecated: 10.0)
@objc
optional func editor(editor: Editor, textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool
A delegation method that is executed when the textView should interact with
a URL in a given character range.
- Parameter editor: An Editor.
- Parameter textView: A UITextView.
- Parameter shouldInteractWith URL: A URL.
- Parameter in characterRange: A Range.
- Parameter interaction: A UITextItemInteraction.
- Returns: A boolean indicating if the textView should interact with a URL in
a given character range, true if yes, false otherwise.
@available(iOS 10.0, *)
@objc
optional func editor(editor: Editor, textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
A delegation method that is executed when the textView should interact with
a text attachment in a given character range.
- Parameter editor: An Editor.
- Parameter textView: A UITextView.
- Parameter shouldInteractWith textAttachment: A NSTextAttachment.
- Parameter in characterRange: A Range.
- Parameter interaction: A UITextItemInteraction.
- Returns: A boolean indicating if the textView should interact with a
NSTextAttachment in a given character range, true if yes, false otherwise.
@available(iOS 10.0, *)
@objc
optional func editor(editor: Editor, textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
*/
}
open class Editor: View {
/// Will layout the view.
open var willLayout: Bool {
return 0 < width && 0 < height && nil != superview
}
/// TextStorage instance that is observed while editing.
open fileprivate(set) var textStorage: TextStorage!
/// A reference to the NSTextContainer.
open fileprivate(set) var textContainer: NSTextContainer!
/// A reference to the NSLayoutManager.
open fileprivate(set) var layoutManager: NSLayoutManager!
/// A preset wrapper around textViewEdgeInsets.
open var textViewEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet {
textViewEdgeInsets = EdgeInsetsPresetToValue(preset: textViewEdgeInsetsPreset)
}
}
/// A reference to textViewEdgeInsets.
@IBInspectable
open var textViewEdgeInsets = EdgeInsets.zero {
didSet {
layoutSubviews()
}
}
/// Reference to the TextView.
open fileprivate(set) var textView: UITextView!
/// A reference to an EditorDelegate.
open weak var delegate: EditorDelegate?
/// The string pattern to match within the textStorage.
open var pattern = "(^|\\s)#[\\d\\w_\u{203C}\u{2049}\u{20E3}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{23E9}-\u{23EC}\u{23F0}\u{23F3}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2601}\u{260E}\u{2611}\u{2614}-\u{2615}\u{261D}\u{263A}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2693}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270C}\u{270F}\u{2712}\u{2714}\u{2716}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E7}-\u{1F1EC}\u{1F1EE}-\u{1F1F0}\u{1F1F3}\u{1F1F5}\u{1F1F7}-\u{1F1FA}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F320}\u{1F330}-\u{1F335}\u{1F337}-\u{1F37C}\u{1F380}-\u{1F393}\u{1F3A0}-\u{1F3C4}\u{1F3C6}-\u{1F3CA}\u{1F3E0}-\u{1F3F0}\u{1F400}-\u{1F43E}\u{1F440}\u{1F442}-\u{1F4F7}\u{1F4F9}-\u{1F4FC}\u{1F500}-\u{1F507}\u{1F509}-\u{1F53D}\u{1F550}-\u{1F567}\u{1F5FB}-\u{1F640}\u{1F645}-\u{1F64F}\u{1F680}-\u{1F68A}]+" {
didSet {
prepareRegularExpression()
}
}
/**
A convenience property that accesses the textStorage
string.
*/
open var string: String {
return textStorage.string
}
/// An Array of matches that match the pattern expression.
open var matches: [String] {
return textStorage.expression!.matches(in: string, options: [], range: NSMakeRange(0, string.utf16.count)).map { [unowned self] in
(self.string as NSString).substring(with: $0.range).trimmed
}
}
/**
An Array of unique matches that match the pattern
expression.
*/
public var uniqueMatches: [String] {
var seen = [String: Bool]()
return matches.filter { nil == seen.updateValue(true, forKey: $0) }
}
open override func layoutSubviews() {
super.layoutSubviews()
guard willLayout else {
return
}
textView.frame = CGRect(x: textViewEdgeInsets.left, y: textViewEdgeInsets.top, width: width - textViewEdgeInsets.left - textViewEdgeInsets.right, height: height - textViewEdgeInsets.top - textViewEdgeInsets.bottom)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
prepareTextContainer()
prepareLayoutManager()
prepareTextStorage()
prepareRegularExpression()
prepareTextView()
}
}
extension Editor {
/// Prepares the textContainer.
fileprivate func prepareTextContainer() {
textContainer = NSTextContainer(size: bounds.size)
}
/// Prepares the layoutManager.
fileprivate func prepareLayoutManager() {
layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
}
/// Prepares the textStorage.
fileprivate func prepareTextStorage() {
textStorage = TextStorage()
textStorage.addLayoutManager(layoutManager)
textStorage.delegate = self
}
/// Prepares the textView.
fileprivate func prepareTextView() {
textView = UITextView(frame: .zero, textContainer: textContainer)
textView.delegate = self
addSubview(textView)
}
/// Prepares the regular expression for matching.
fileprivate func prepareRegularExpression() {
textStorage.expression = try? NSRegularExpression(pattern: pattern, options: [])
}
}
extension Editor: TextStorageDelegate {
@objc
open func textStorage(textStorage: TextStorage, willProcessEditing text: String, range: NSRange) {
delegate?.editor?(editor: self, willProcessEditing: textStorage, text: string, range: range)
}
@objc
open func textStorage(textStorage: TextStorage, didProcessEditing text: String, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) {
guard let range = result?.range else {
return
}
delegate?.editor?(editor: self, didProcessEditing: textStorage, text: string, range: range)
}
}
extension Editor: UITextViewDelegate {
@objc
open func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return delegate?.editor?(editor: self, shouldBeginEditing: textView) ?? true
}
@objc
open func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
return delegate?.editor?(editor: self, shouldEndEditing: textView) ?? true
}
@objc
open func textViewDidBeginEditing(_ textView: UITextView) {
delegate?.editor?(editor: self, didBeginEditing: textView)
}
@objc
open func textViewDidEndEditing(_ textView: UITextView) {
delegate?.editor?(editor: self, didEndEditing: textView)
}
@objc
open func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return delegate?.editor?(editor: self, textView: textView, shouldChangeTextIn: range, replacementText: text) ?? true
}
@objc
open func textViewDidChange(_ textView: UITextView) {
delegate?.editor?(editor: self, didChange: textView)
}
@objc
open func textViewDidChangeSelection(_ textView: UITextView) {
delegate?.editor?(editor: self, didChangeSelection: textView)
}
}
/*
@available(iOS, introduced: 8.0, deprecated: : 10.0)
extension Editor {
@objc
open func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
return delegate?.editor?(editor: self, textView: textView, shouldInteractWith: URL, in: characterRange) ?? true
}
@objc
open func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
return delegate?.editor?(editor: self, textView: textView, shouldInteractWith: textAttachment, in: characterRange) ?? true
}
}
@available(iOS 10.0, *)
extension Editor {
@objc
open func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return delegate?.editor?(editor: self, textView: textView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? true
}
@objc
open func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return delegate?.editor?(editor: self, textView: textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? true
}
}
*/
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
import AVFoundation
extension UIViewController {
/**
A convenience property that provides access to the EditorController.
This is the recommended method of accessing the EditorController
through child UIViewControllers.
*/
public var editorController: EditorController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is EditorController {
return viewController as? EditorController
}
viewController = viewController?.parent
}
return nil
}
}
open class EditorController: ToolbarController {
/// A reference to the Editor instance.
@IBInspectable
open let editor = Editor()
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
view.backgroundColor = .white
prepareToolbar()
prepareEditor()
}
/// Prepares the toolbar.
private func prepareToolbar() {
toolbar.depthPreset = .none
}
/// Prepares editor.
private func prepareEditor() {
editor.delegate = self
}
}
extension EditorController: EditorDelegate {}
...@@ -97,7 +97,7 @@ private class FontLoader { ...@@ -97,7 +97,7 @@ private class FontLoader {
if !CTFontManagerRegisterGraphicsFont(font, &error) { if !CTFontManagerRegisterGraphicsFont(font, &error) {
let errorDescription = CFErrorCopyDescription(error!.takeUnretainedValue()) let errorDescription = CFErrorCopyDescription(error!.takeUnretainedValue())
let nsError = error!.takeUnretainedValue() as Any as! Error let nsError = error!.takeUnretainedValue() as Any as! Error
NSException(name: .internalInconsistencyException, reason: errorDescription as? String, userInfo: [NSUnderlyingErrorKey: nsError as Any]).raise() NSException(name: .internalInconsistencyException, reason: errorDescription as String?, userInfo: [NSUnderlyingErrorKey: nsError as Any]).raise()
} }
} }
} }
......
...@@ -105,6 +105,6 @@ open class ImageCard: Card { ...@@ -105,6 +105,6 @@ open class ImageCard: Card {
} }
container.height = h container.height = h
height = h bounds.size.height = h
} }
} }
...@@ -44,7 +44,7 @@ extension Array where Element: Equatable { ...@@ -44,7 +44,7 @@ extension Array where Element: Equatable {
} }
guard -1 < start else { guard -1 < start else {
fatalError("Range out of bounds for \(start) - \(end), should be 0 - \(count).") fatalError("Range out of bounds for \(start) - \(end ?? 0), should be 0 - \(count).")
} }
var diff = abs(e - start) var diff = abs(e - start)
......
...@@ -173,7 +173,7 @@ extension UIViewController { ...@@ -173,7 +173,7 @@ extension UIViewController {
- Returns: An optional UIViewControllerAnimatedTransitioning. - Returns: An optional UIViewControllerAnimatedTransitioning.
*/ */
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionEnabled ? Motion(isPresenting: true, isContainer: false) : nil return isMotionEnabled ? PresentingMotion(isPresenting: true, isContainer: false) : nil
} }
/** /**
...@@ -182,7 +182,7 @@ extension UIViewController { ...@@ -182,7 +182,7 @@ extension UIViewController {
- Returns: An optional UIViewControllerAnimatedTransitioning. - Returns: An optional UIViewControllerAnimatedTransitioning.
*/ */
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionEnabled ? Motion() : nil return isMotionEnabled ? DismissingMotion(isPresenting: true, isContainer: false) : nil
} }
/** /**
...@@ -309,8 +309,7 @@ open class MotionPresentationController: UIPresentationController { ...@@ -309,8 +309,7 @@ open class MotionPresentationController: UIPresentationController {
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in }) presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in })
} }
open override func presentationTransitionDidEnd(_ completed: Bool) { open override func presentationTransitionDidEnd(_ completed: Bool) {}
}
open override func dismissalTransitionWillBegin() { open override func dismissalTransitionWillBegin() {
guard nil != containerView else { guard nil != containerView else {
...@@ -320,8 +319,7 @@ open class MotionPresentationController: UIPresentationController { ...@@ -320,8 +319,7 @@ open class MotionPresentationController: UIPresentationController {
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in }) presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in })
} }
open override func dismissalTransitionDidEnd(_ completed: Bool) { open override func dismissalTransitionDidEnd(_ completed: Bool) {}
}
open override var frameOfPresentedViewInContainerView: CGRect { open override var frameOfPresentedViewInContainerView: CGRect {
return containerView?.bounds ?? .zero return containerView?.bounds ?? .zero
...@@ -338,9 +336,15 @@ public protocol MotionDelegate { ...@@ -338,9 +336,15 @@ public protocol MotionDelegate {
@objc @objc
optional func motionDelayTransitionByTimeInterval(motion: Motion) -> TimeInterval optional func motionDelayTransitionByTimeInterval(motion: Motion) -> TimeInterval
@objc
optional func motionWillBeginPresentation(presentationController: UIPresentationController)
@objc
optional func motionAnimateAlongsideTransition(presentationController: UIPresentationController)
} }
open class Motion: NSObject { open class MotionAnimator: NSObject {
/// A boolean indicating whether Motion is presenting a view controller. /// A boolean indicating whether Motion is presenting a view controller.
open fileprivate(set) var isPresenting: Bool open fileprivate(set) var isPresenting: Bool
...@@ -386,7 +390,7 @@ open class Motion: NSObject { ...@@ -386,7 +390,7 @@ open class Motion: NSObject {
/// The view that is being transitioned to. /// The view that is being transitioned to.
open var toView: UIView { open var toView: UIView {
return transitionContext.view(forKey: .to)! return toViewController.view
} }
/// The subviews of the view being transitioned to. /// The subviews of the view being transitioned to.
...@@ -396,7 +400,7 @@ open class Motion: NSObject { ...@@ -396,7 +400,7 @@ open class Motion: NSObject {
/// The view that is being transitioned from. /// The view that is being transitioned from.
open var fromView: UIView { open var fromView: UIView {
return transitionContext.view(forKey: .from)! return fromViewController.view
} }
/// The subviews of the view being transitioned from. /// The subviews of the view being transitioned from.
...@@ -404,11 +408,6 @@ open class Motion: NSObject { ...@@ -404,11 +408,6 @@ open class Motion: NSObject {
return Motion.subviews(of: fromView) return Motion.subviews(of: fromView)
} }
/// A time value to delay the transition animation by.
fileprivate var delayTransitionByTimeInterval: TimeInterval {
return fromViewController.motionDelegate?.motionDelayTransitionByTimeInterval?(motion: self) ?? 0
}
/// The default initializer. /// The default initializer.
public override init() { public override init() {
isPresenting = false isPresenting = false
...@@ -511,7 +510,7 @@ open class Motion: NSObject { ...@@ -511,7 +510,7 @@ open class Motion: NSObject {
} }
} }
extension Motion: UIViewControllerAnimatedTransitioning { extension MotionAnimator: UIViewControllerAnimatedTransitioning {
/** /**
The animation method that is used to coordinate the transition. The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning. - Parameter using transitionContext: A UIViewControllerContextTransitioning.
...@@ -519,21 +518,6 @@ extension Motion: UIViewControllerAnimatedTransitioning { ...@@ -519,21 +518,6 @@ extension Motion: UIViewControllerAnimatedTransitioning {
@objc(animateTransition:) @objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext self.transitionContext = transitionContext
fromViewController.motionDelegate?.motion?(motion: self, willTransition: fromView, toView: toView)
Motion.delay(delayTransitionByTimeInterval) { [weak self] in
guard let s = self else {
return
}
s.prepareContainerView()
s.prepareTransitionSnapshot()
s.prepareTransitionPairs()
s.prepareTransitionView()
s.prepareTransitionBackgroundView()
s.prepareToView()
s.prepareTransitionAnimation()
}
} }
/** /**
...@@ -547,65 +531,7 @@ extension Motion: UIViewControllerAnimatedTransitioning { ...@@ -547,65 +531,7 @@ extension Motion: UIViewControllerAnimatedTransitioning {
} }
} }
extension Motion { extension MotionAnimator {
/// Prepares the containerView.
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
/// Prepares the transitionSnapshot.
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.transitionSnapshot(afterUpdates: true, shouldHide: false)
transitionSnapshot.frame = fromView.frame
containerView.insertSubview(transitionSnapshot, aboveSubview: fromView)
}
/// Prepares the transitionPairs.
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
for to in toSubviews {
guard to.motionIdentifier == from.motionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
/// Prepares the transitionView.
fileprivate func prepareTransitionView() {
transitionView.frame = toView.bounds
transitionView.isUserInteractionEnabled = false
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
/// Prepares the transitionBackgroundView.
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = isPresenting ? .clear : fromView.backgroundColor ?? .clear
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
/// Prepares the toView.
fileprivate func prepareToView() {
toView.isHidden = isPresenting
containerView.insertSubview(toView, belowSubview: transitionView)
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
/// Prepares the transition animation.
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundAnimation()
cleanUpAnimation()
removeTransitionSnapshot()
}
}
extension Motion {
/// Adds the available transition animations. /// Adds the available transition animations.
fileprivate func addTransitionAnimations() { fileprivate func addTransitionAnimations() {
for (from, to) in transitionPairs { for (from, to) in transitionPairs {
...@@ -681,26 +607,7 @@ extension Motion { ...@@ -681,26 +607,7 @@ extension Motion {
} }
} }
extension Motion { extension MotionAnimator {
/**
Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup.
*/
open class func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup()
group.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
group.isRemovedOnCompletion = false
group.animations = animations
group.duration = duration
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group
}
}
extension Motion {
/** /**
Calculates the animation delay time based on the given Array of MotionAnimations. Calculates the animation delay time based on the given Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations. - Parameter animations: An Array of MotionAnimations.
...@@ -759,6 +666,115 @@ extension Motion { ...@@ -759,6 +666,115 @@ extension Motion {
} }
} }
open class Motion: MotionAnimator {
/// A time value to delay the transition animation by.
fileprivate var delayTransitionByTimeInterval: TimeInterval {
return fromViewController.motionDelegate?.motionDelayTransitionByTimeInterval?(motion: self) ?? 0
}
/**
The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning.
*/
@objc(animateTransition:)
open override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
super.animateTransition(using: transitionContext)
fromViewController.motionDelegate?.motion?(motion: self, willTransition: fromView, toView: toView)
Motion.delay(delayTransitionByTimeInterval) { [weak self] in
guard let s = self else {
return
}
s.prepareContainerView()
s.prepareTransitionSnapshot()
s.prepareTransitionPairs()
s.prepareTransitionView()
s.prepareTransitionBackgroundView()
s.prepareToView()
s.prepareTransitionAnimation()
}
}
/// Prepares the toView.
fileprivate func prepareToView() {
toView.isHidden = isPresenting
containerView.insertSubview(toView, belowSubview: transitionView)
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
}
extension Motion {
/// Prepares the containerView.
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
/// Prepares the transitionSnapshot.
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.transitionSnapshot(afterUpdates: true, shouldHide: false)
transitionSnapshot.frame = fromView.frame
containerView.addSubview(transitionSnapshot)
}
/// Prepares the transitionPairs.
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
for to in toSubviews {
guard to.motionIdentifier == from.motionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
/// Prepares the transitionView.
fileprivate func prepareTransitionView() {
transitionView.frame = toView.bounds
transitionView.isUserInteractionEnabled = false
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
/// Prepares the transitionBackgroundView.
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = isPresenting ? .clear : fromView.backgroundColor ?? .clear
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
/// Prepares the transition animation.
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundAnimation()
cleanUpAnimation()
removeTransitionSnapshot()
}
}
extension Motion {
/**
Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup.
*/
open class func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup()
group.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
group.isRemovedOnCompletion = false
group.animations = animations
group.duration = duration
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group
}
}
extension Motion { extension Motion {
/// Cleans up the animation transition. /// Cleans up the animation transition.
fileprivate func cleanUpAnimation() { fileprivate func cleanUpAnimation() {
...@@ -805,3 +821,17 @@ extension Motion { ...@@ -805,3 +821,17 @@ extension Motion {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled) transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
} }
} }
open class PresentingMotion: Motion {}
open class DismissingMotion: Motion {
/// Prepares the toView.
fileprivate override func prepareToView() {
toView.isHidden = true
toView.frame = fromView.frame
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
}
...@@ -374,10 +374,10 @@ extension UIView { ...@@ -374,10 +374,10 @@ extension UIView {
/// Computes the rotation of the view. /// Computes the rotation of the view.
open var motionRotationAngle: CGFloat { open var motionRotationAngle: CGFloat {
get { get {
return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(M_PI) return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(Double.pi)
} }
set(value) { set(value) {
transform = CGAffineTransform(rotationAngle: CGFloat(M_PI) * value / 180) transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * value / 180)
} }
} }
...@@ -506,7 +506,7 @@ extension Motion { ...@@ -506,7 +506,7 @@ extension Motion {
*/ */
public static func rotation(angle: CGFloat) -> CABasicAnimation { public static func rotation(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation) let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation return animation
} }
...@@ -517,7 +517,7 @@ extension Motion { ...@@ -517,7 +517,7 @@ extension Motion {
*/ */
public static func rotationX(angle: CGFloat) -> CABasicAnimation { public static func rotationX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX) let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation return animation
} }
...@@ -528,7 +528,7 @@ extension Motion { ...@@ -528,7 +528,7 @@ extension Motion {
*/ */
public static func rotationY(angle: CGFloat) -> CABasicAnimation { public static func rotationY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY) let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation return animation
} }
...@@ -539,7 +539,7 @@ extension Motion { ...@@ -539,7 +539,7 @@ extension Motion {
*/ */
public static func rotationZ(angle: CGFloat) -> CABasicAnimation { public static func rotationZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ) let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * angle / 180))
return animation return animation
} }
...@@ -550,7 +550,7 @@ extension Motion { ...@@ -550,7 +550,7 @@ extension Motion {
*/ */
public static func spin(rotations: CGFloat) -> CABasicAnimation { public static func spin(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation) let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation return animation
} }
...@@ -561,7 +561,7 @@ extension Motion { ...@@ -561,7 +561,7 @@ extension Motion {
*/ */
public static func spinX(rotations: CGFloat) -> CABasicAnimation { public static func spinX(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX) let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation return animation
} }
...@@ -572,7 +572,7 @@ extension Motion { ...@@ -572,7 +572,7 @@ extension Motion {
*/ */
public static func spinY(rotations: CGFloat) -> CABasicAnimation { public static func spinY(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY) let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation return animation
} }
...@@ -583,7 +583,7 @@ extension Motion { ...@@ -583,7 +583,7 @@ extension Motion {
*/ */
public static func spinZ(rotations: CGFloat) -> CABasicAnimation { public static func spinZ(rotations: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ) let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations)) animation.toValue = NSNumber(value: Double(CGFloat(Double.pi) * 2 * rotations))
return animation return animation
} }
......
...@@ -39,7 +39,7 @@ extension UIFont { ...@@ -39,7 +39,7 @@ extension UIFont {
- Returns a CGSize. - Returns a CGSize.
*/ */
open func stringSize(string: String, constrainedTo width: CGFloat) -> CGSize { open func stringSize(string: String, constrainedTo width: CGFloat) -> CGSize {
return string.boundingRect(with: CGSize(width: width, height: CGFloat(DBL_MAX)), return string.boundingRect(with: CGSize(width: width, height: CGFloat(Double.greatestFiniteMagnitude)),
options: NSStringDrawingOptions.usesLineFragmentOrigin, options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: self], attributes: [NSFontAttributeName: self],
context: nil).size context: nil).size
......
...@@ -227,13 +227,13 @@ extension UIImage { ...@@ -227,13 +227,13 @@ extension UIImage {
switch imageOrientation { switch imageOrientation {
case .down, .downMirrored: case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height) transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat(M_PI)) transform = transform.rotated(by: CGFloat(Double.pi))
case .left, .leftMirrored: case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0) transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat(M_PI_2)) transform = transform.rotated(by: CGFloat(Double.pi / 2))
case .right, .rightMirrored: case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height) transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: -CGFloat(M_PI_2)) transform = transform.rotated(by: -CGFloat(Double.pi / 2))
default:break default:break
} }
...@@ -296,8 +296,8 @@ extension UIImage { ...@@ -296,8 +296,8 @@ extension UIImage {
let screenScale = Screen.scale let screenScale = Screen.scale
let imageRect = CGRect(origin: .zero, size: size) let imageRect = CGRect(origin: .zero, size: size)
let hasBlur = radius > CGFloat(FLT_EPSILON) let hasBlur = radius > CGFloat(Float.ulpOfOne)
let hasSaturationChange = fabs(saturationDeltaFactor - 1.0) > CGFloat(FLT_EPSILON) let hasSaturationChange = fabs(saturationDeltaFactor - 1.0) > CGFloat(Float.ulpOfOne)
if hasBlur || hasSaturationChange { if hasBlur || hasSaturationChange {
UIGraphicsBeginImageContextWithOptions(size, false, screenScale) UIGraphicsBeginImageContextWithOptions(size, false, screenScale)
...@@ -315,7 +315,7 @@ extension UIImage { ...@@ -315,7 +315,7 @@ extension UIImage {
var outBuffer = createEffectBuffer(context: outContext) var outBuffer = createEffectBuffer(context: outContext)
if hasBlur { if hasBlur {
let a = sqrt(2 * M_PI) let a = sqrt(2 * .pi)
let b = CGFloat(a) / 4 let b = CGFloat(a) / 4
let c = radius * screenScale let c = radius * screenScale
let d = c * 3.0 * b let d = c * 3.0 * b
......
...@@ -113,8 +113,18 @@ open class PageTabBarController: RootController { ...@@ -113,8 +113,18 @@ open class PageTabBarController: RootController {
/// Indicates that the tab has been pressed and animating. /// Indicates that the tab has been pressed and animating.
open internal(set) var isTabSelectedAnimation = false open internal(set) var isTabSelectedAnimation = false
/// An internal reference to the selectedIndex.
fileprivate var internalSelectedIndex = 0
/// The currently selected UIViewController. /// The currently selected UIViewController.
open internal(set) var selectedIndex = 0 open var selectedIndex: Int {
get {
return internalSelectedIndex
}
set(value) {
setInternalSelectedIndex(index: value, animated: true)
}
}
/// PageTabBar alignment setting. /// PageTabBar alignment setting.
open var pageTabBarAlignment = PageTabBarAlignment.bottom open var pageTabBarAlignment = PageTabBarAlignment.bottom
...@@ -155,16 +165,15 @@ open class PageTabBarController: RootController { ...@@ -155,16 +165,15 @@ open class PageTabBarController: RootController {
isBounceEnabled = true isBounceEnabled = true
super.init(rootViewController: UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)) super.init(rootViewController: UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil))
viewControllers.append(rootViewController) viewControllers.append(rootViewController)
setViewControllers(viewControllers, direction: .forward, animated: true) setInternalSelectedIndex(index: 0, animated: true)
prepare() prepare()
} }
public init(viewControllers: [UIViewController], selectedIndex: Int = 0) { public init(viewControllers: [UIViewController], selectedIndex index: Int = 0) {
isBounceEnabled = true isBounceEnabled = true
super.init(rootViewController: UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)) super.init(rootViewController: UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil))
self.selectedIndex = selectedIndex
self.viewControllers.append(contentsOf: viewControllers) self.viewControllers.append(contentsOf: viewControllers)
setViewControllers([self.viewControllers[selectedIndex]], direction: .forward, animated: true) setInternalSelectedIndex(index: index, animated: true)
prepare() prepare()
} }
...@@ -198,7 +207,6 @@ open class PageTabBarController: RootController { ...@@ -198,7 +207,6 @@ open class PageTabBarController: RootController {
*/ */
open func setViewControllers(_ viewControllers: [UIViewController], direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) { open func setViewControllers(_ viewControllers: [UIViewController], direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
pageViewController?.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion) pageViewController?.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
prepare()
} }
/** /**
...@@ -240,44 +248,65 @@ open class PageTabBarController: RootController { ...@@ -240,44 +248,65 @@ open class PageTabBarController: RootController {
button.addTarget(self, action: #selector(handlePageTabBarButton(button:)), for: .touchUpInside) button.addTarget(self, action: #selector(handlePageTabBarButton(button:)), for: .touchUpInside)
} }
} }
}
extension PageTabBarController {
/// Prepares the pageTabBar.
fileprivate func preparePageTabBar() {
pageTabBar.zPosition = 1000
pageTabBar.dividerColor = Color.grey.lighten3
view.addSubview(pageTabBar)
pageTabBar.select(at: internalSelectedIndex)
}
}
extension PageTabBarController {
/** /**
Handles the pageTabBarButton. Handles the pageTabBarButton.
- Parameter button: A UIButton. - Parameter button: A UIButton.
*/ */
@objc @objc
internal func handlePageTabBarButton(button: UIButton) { fileprivate func handlePageTabBarButton(button: UIButton) {
guard let index = pageTabBar.buttons.index(of: button) else { guard let index = pageTabBar.buttons.index(of: button) else {
return return
} }
guard index != selectedIndex else { guard index != internalSelectedIndex else {
return return
} }
let direction: UIPageViewControllerNavigationDirection = index < selectedIndex ? .reverse : .forward setInternalSelectedIndex(index: index, animated: true)
}
}
extension PageTabBarController {
/**
Internally sets the internalSelectedIndex value.
- Parameter index: Int.
- Parameter animated: Bool.
*/
fileprivate func setInternalSelectedIndex(index: Int, animated: Bool = false) {
guard animated else {
internalSelectedIndex = index
return
}
let direction: UIPageViewControllerNavigationDirection = index < internalSelectedIndex ? .reverse : .forward
isTabSelectedAnimation = true isTabSelectedAnimation = true
selectedIndex = index
pageTabBar.select(at: selectedIndex) internalSelectedIndex = index
pageTabBar.select(at: internalSelectedIndex)
setViewControllers([viewControllers[index]], direction: direction, animated: true) { [weak self] _ in setViewControllers([viewControllers[internalSelectedIndex]], direction: direction, animated: true) { [weak self] _ in
guard let s = self else { guard let s = self else {
return return
} }
s.isTabSelectedAnimation = false s.isTabSelectedAnimation = false
s.delegate?.pageTabBarController?(pageTabBarController: s, didTransitionTo: s.viewControllers[s.selectedIndex]) s.delegate?.pageTabBarController?(pageTabBarController: s, didTransitionTo: s.viewControllers[s.internalSelectedIndex])
} }
} }
/// Prepares the pageTabBar.
private func preparePageTabBar() {
pageTabBar.zPosition = 1000
pageTabBar.dividerColor = Color.grey.lighten3
view.addSubview(pageTabBar)
pageTabBar.select(at: selectedIndex)
}
} }
extension PageTabBarController: UIPageViewControllerDelegate { extension PageTabBarController: UIPageViewControllerDelegate {
...@@ -290,8 +319,9 @@ extension PageTabBarController: UIPageViewControllerDelegate { ...@@ -290,8 +319,9 @@ extension PageTabBarController: UIPageViewControllerDelegate {
return return
} }
selectedIndex = index setInternalSelectedIndex(index: index)
pageTabBar.select(at: selectedIndex)
pageTabBar.select(at: index)
if finished && completed { if finished && completed {
delegate?.pageTabBarController?(pageTabBarController: self, didTransitionTo: v) delegate?.pageTabBarController?(pageTabBarController: self, didTransitionTo: v)
......
...@@ -79,6 +79,6 @@ open class PresenterCard: Card { ...@@ -79,6 +79,6 @@ open class PresenterCard: Card {
} }
container.height = h container.height = h
height = h bounds.size.height = h
} }
} }
/* /*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>. * Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
......
...@@ -382,7 +382,8 @@ open class TextField: UITextField { ...@@ -382,7 +382,8 @@ open class TextField: UITextField {
} }
open override func becomeFirstResponder() -> Bool { open override func becomeFirstResponder() -> Bool {
layoutSubviews() setNeedsLayout()
layoutIfNeeded()
return super.becomeFirstResponder() return super.becomeFirstResponder()
} }
...@@ -398,6 +399,8 @@ open class TextField: UITextField { ...@@ -398,6 +399,8 @@ open class TextField: UITextField {
borderStyle = .none borderStyle = .none
backgroundColor = nil backgroundColor = nil
contentScaleFactor = Screen.scale contentScaleFactor = Screen.scale
font = RobotoFont.regular(with: 16)
textColor = Color.darkText.primary
prepareDivider() prepareDivider()
preparePlaceholderLabel() preparePlaceholderLabel()
...@@ -425,8 +428,8 @@ extension TextField { ...@@ -425,8 +428,8 @@ extension TextField {
/// Prepares the placeholderLabel. /// Prepares the placeholderLabel.
fileprivate func preparePlaceholderLabel() { fileprivate func preparePlaceholderLabel() {
font = RobotoFont.regular(with: 16)
placeholderNormalColor = Color.darkText.others placeholderNormalColor = Color.darkText.others
placeholderLabel.backgroundColor = .clear
addSubview(placeholderLabel) addSubview(placeholderLabel)
} }
...@@ -506,7 +509,7 @@ extension TextField { ...@@ -506,7 +509,7 @@ extension TextField {
/// Layout the detailLabel. /// Layout the detailLabel.
fileprivate func layoutDetailLabel() { fileprivate func layoutDetailLabel() {
let c = dividerContentEdgeInsets let c = dividerContentEdgeInsets
detailLabel.height = detailLabel.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)).height detailLabel.height = detailLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)).height
detailLabel.x = c.left detailLabel.x = c.left
detailLabel.y = height + detailVerticalOffset detailLabel.y = height + detailVerticalOffset
detailLabel.width = width - c.left - c.right detailLabel.width = width - c.left - c.right
......
...@@ -31,10 +31,75 @@ ...@@ -31,10 +31,75 @@
import UIKit import UIKit
@objc(TextViewDelegate) @objc(TextViewDelegate)
public protocol TextViewDelegate : UITextViewDelegate {} public protocol TextViewDelegate : UITextViewDelegate {
/**
A delegation method that is executed when the keyboard will open.
- Parameter textView: A TextView.
- Parameter willShowKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, willShowKeyboard value: NSValue)
/**
A delegation method that is executed when the keyboard will close.
- Parameter textView: A TextView.
- Parameter willHideKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, willHideKeyboard value: NSValue)
/**
A delegation method that is executed when the keyboard did open.
- Parameter textView: A TextView.
- Parameter didShowKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, didShowKeyboard value: NSValue)
/**
A delegation method that is executed when the keyboard did close.
- Parameter textView: A TextView.
- Parameter didHideKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, didHideKeyboard value: NSValue)
/**
A delegation method that is executed when text will be
processed during editing.
- Parameter textView: A TextView.
- Parameter willProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func textView(textView: TextView, willProcessEditing textStorage: TextStorage, text: String, range: NSRange)
/**
A delegation method that is executed when text has been
processed after editing.
- Parameter textView: A TextView.
- Parameter didProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func textView(textView: TextView, didProcessEditing textStorage: TextStorage, text: String, range: NSRange)
}
@objc(TextView) @objc(TextView)
open class TextView: UITextView { open class TextView: UITextView {
/// A boolean indicating whether the text is empty.
open var isEmpty: Bool {
return 0 == text?.utf16.count
}
/// A boolean indicating whether the text is in edit mode.
open fileprivate(set) var isEditing = true
/// Is the keyboard hidden.
open fileprivate(set) var isKeyboardHidden = true
/// A property that accesses the backing layer's background /// A property that accesses the backing layer's background
@IBInspectable @IBInspectable
open override var backgroundColor: UIColor? { open override var backgroundColor: UIColor? {
...@@ -43,83 +108,115 @@ open class TextView: UITextView { ...@@ -43,83 +108,115 @@ open class TextView: UITextView {
} }
} }
/** /// The placeholderLabel font value.
The title UILabel that is displayed when there is text. The
titleLabel text value is updated with the placeholderLabel
text value before being displayed.
*/
@IBInspectable @IBInspectable
open var titleLabel: UILabel? { open override var font: UIFont? {
didSet { didSet {
prepareTitleLabel() placeholderLabel.font = font
} }
} }
/// The color of the titleLabel text when the textView is not active. /// The placeholderLabel text value.
@IBInspectable @IBInspectable
open var titleLabelColor: UIColor? { open var placeholder: String? {
didSet { get {
titleLabel?.textColor = titleLabelColor return placeholderLabel.text
}
set(value) {
placeholderLabel.text = value
} }
} }
/// The color of the titleLabel text when the textView is active. /// The placeholder UILabel.
@IBInspectable @IBInspectable
open var titleLabelActiveColor: UIColor? open let placeholderLabel = UILabel()
/** /// Placeholder normal text
A property that sets the distance between the textView and
titleLabel.
*/
@IBInspectable @IBInspectable
open var titleLabelAnimationDistance: CGFloat = 8 open var placeholderNormalColor = Color.darkText.others {
/// Placeholder UILabel view.
open var placeholderLabel: UILabel? {
didSet { didSet {
preparePlaceholderLabel() updatePlaceholderLabelColor()
} }
} }
/// An override to the text property. /// Placeholder active text
@IBInspectable @IBInspectable
open override var text: String! { open var placeholderActiveColor = Color.blue.base {
didSet { didSet {
handleTextViewTextDidChange() updatePlaceholderLabelColor()
} }
} }
/// An override to the attributedText property. /// NSTextContainer EdgeInsets preset property.
open override var attributedText: NSAttributedString! { open var textContainerInsetsPreset = EdgeInsetsPreset.none {
didSet { didSet {
handleTextViewTextDidChange() textContainerInsets = EdgeInsetsPresetToValue(preset: textContainerInsetsPreset)
}
}
/// NSTextContainer EdgeInsets property.
open var textContainerInsets: EdgeInsets {
get {
return textContainerInset
}
set(value) {
textContainerInset = value
} }
} }
/** /**
Text container UIEdgeInset preset property. This updates the An initializer that initializes the object with a NSCoder object.
textContainerInset property with a preset value. - Parameter aDecoder: A NSCoder instance.
*/ */
open var textContainerEdgeInsetsPreset: EdgeInsetsPreset = .none { public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
/// The string pattern to match within the textStorage.
open var pattern = "(^|\\s)#[\\d\\w_\u{203C}\u{2049}\u{20E3}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{23E9}-\u{23EC}\u{23F0}\u{23F3}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2601}\u{260E}\u{2611}\u{2614}-\u{2615}\u{261D}\u{263A}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2693}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270C}\u{270F}\u{2712}\u{2714}\u{2716}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E7}-\u{1F1EC}\u{1F1EE}-\u{1F1F0}\u{1F1F3}\u{1F1F5}\u{1F1F7}-\u{1F1FA}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F320}\u{1F330}-\u{1F335}\u{1F337}-\u{1F37C}\u{1F380}-\u{1F393}\u{1F3A0}-\u{1F3C4}\u{1F3C6}-\u{1F3CA}\u{1F3E0}-\u{1F3F0}\u{1F400}-\u{1F43E}\u{1F440}\u{1F442}-\u{1F4F7}\u{1F4F9}-\u{1F4FC}\u{1F500}-\u{1F507}\u{1F509}-\u{1F53D}\u{1F550}-\u{1F567}\u{1F5FB}-\u{1F640}\u{1F645}-\u{1F64F}\u{1F680}-\u{1F68A}]+" {
didSet { didSet {
textContainerInset = EdgeInsetsPresetToValue(preset: textContainerEdgeInsetsPreset) prepareRegularExpression()
} }
} }
/// Text container UIEdgeInset property. /// A reference to the textView text.
open override var textContainerInset: EdgeInsets { open override var text: String! {
didSet { didSet {
reload() setContentOffset(.zero, animated: true)
updatePlaceholderVisibility()
} }
} }
/** /**
An initializer that initializes the object with a NSCoder object. A convenience property that accesses the textStorage
- Parameter aDecoder: A NSCoder instance. string.
*/ */
public required init?(coder aDecoder: NSCoder) { open var string: String {
super.init(coder: aDecoder) return textStorage.string
prepare() }
/// An Array of matches that match the pattern expression.
open var matches: [String] {
guard let v = (textStorage as? TextStorage)?.expression else {
return []
}
return v.matches(in: string, options: [], range: NSMakeRange(0, string.utf16.count)).map { [unowned self] in
(self.string as NSString).substring(with: $0.range).trimmed
}
}
/**
An Array of unique matches that match the pattern
expression.
*/
open var uniqueMatches: [String] {
var set = Set<String>()
for x in matches {
set.insert(x)
}
return Array<String>(set)
} }
/** /**
...@@ -142,168 +239,217 @@ open class TextView: UITextView { ...@@ -142,168 +239,217 @@ open class TextView: UITextView {
self.init(frame: .zero, textContainer: textContainer) self.init(frame: .zero, textContainer: textContainer)
} }
/** Denitializer. This should never be called unless you know /// A convenience initializer that constructs all aspects of the textView.
public convenience init() {
let textContainer = NSTextContainer(size: .zero)
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = TextStorage()
textStorage.addLayoutManager(layoutManager)
self.init(textContainer: textContainer)
textContainer.size = bounds.size
textStorage.delegate = self
}
/**
Denitializer. This should never be called unless you know
what you are doing. what you are doing.
*/ */
deinit { deinit {
removeNotificationHandlers() NotificationCenter.default.removeObserver(self)
} }
open override func layoutSubviews() { open override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
layoutShape() layoutShape()
layoutShadowPath() layoutShadowPath()
layoutPlaceholderLabel()
placeholderLabel?.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2
titleLabel?.frame.size.width = bounds.width
} }
/// Reloads necessary components when the view has changed. /**
open func reload() { Prepares the view instance when intialized. When subclassing,
if let p = placeholderLabel { it is recommended to override the prepare method
removeConstraints(constraints) to initialize property values and other setup operations.
layout(p).edges( The super.prepare method should always be called immediately
top: textContainerInset.top, when subclassing.
left: textContainerInset.left + textContainer.lineFragmentPadding, */
bottom: textContainerInset.bottom, open func prepare() {
right: textContainerInset.right + textContainer.lineFragmentPadding) contentScaleFactor = Screen.scale
textContainerInset = .zero
backgroundColor = nil
font = RobotoFont.regular(with: 16)
textColor = Color.darkText.primary
prepareNotificationHandlers()
prepareRegularExpression()
preparePlaceholderLabel()
} }
}
extension TextView {
/// Prepares the Notification handlers.
fileprivate func prepareNotificationHandlers() {
let defaultCenter = NotificationCenter.default
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleKeyboardDidShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleKeyboardDidHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidBegin), name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidEnd), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
} }
/// Notification handler for when text editing began. /// Prepares the regular expression for matching.
@objc fileprivate func prepareRegularExpression() {
fileprivate func handleTextViewTextDidBegin() { (textStorage as? TextStorage)?.expression = try? NSRegularExpression(pattern: pattern, options: [])
titleLabel?.textColor = titleLabelActiveColor
} }
/// Notification handler for when text changed. /// prepares the placeholderLabel property.
@objc fileprivate func preparePlaceholderLabel() {
fileprivate func handleTextViewTextDidChange() { placeholderLabel.textColor = Color.darkText.others
if let p = placeholderLabel { placeholderLabel.textAlignment = textAlignment
p.isHidden = !(true == text?.isEmpty) placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = .clear
addSubview(placeholderLabel)
} }
}
guard let t = text else { extension TextView {
hideTitleLabel() /// Updates the placeholderLabel text color.
return fileprivate func updatePlaceholderLabelColor() {
tintColor = placeholderActiveColor
placeholderLabel.textColor = isEditing ? placeholderActiveColor : placeholderNormalColor
} }
if 0 < t.utf16.count { /// Updates the placeholderLabel visibility.
showTitleLabel() fileprivate func updatePlaceholderVisibility() {
} else { placeholderLabel.isHidden = !isEmpty
hideTitleLabel()
} }
}
extension TextView {
/// Laysout the placeholder UILabel.
fileprivate func layoutPlaceholderLabel() {
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2
let x = textContainerInset.left + textContainer.lineFragmentPadding
let y = textContainerInset.top
placeholderLabel.sizeToFit()
placeholderLabel.frame.origin.x = x
placeholderLabel.frame.origin.y = y
placeholderLabel.frame.size.width = textContainer.size.width - textContainerInset.right - textContainer.lineFragmentPadding
} }
}
/// Notification handler for when text editing ended. extension TextView {
/**
Handler for when the keyboard will open.
- Parameter notification: A Notification.
*/
@objc @objc
fileprivate func handleTextViewTextDidEnd() { fileprivate func handleKeyboardWillShow(notification: Notification) {
guard let t = text else { guard isKeyboardHidden else {
hideTitleLabel()
return return
} }
if 0 < t.utf16.count { guard let v = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
showTitleLabel() return
} else {
hideTitleLabel()
} }
titleLabel?.textColor = titleLabelColor (delegate as? TextViewDelegate)?.textView?(textView: self, willShowKeyboard: v)
} }
/** /**
Prepares the view instance when intialized. When subclassing, Handler for when the keyboard did open.
it is recommended to override the prepare method - Parameter notification: A Notification.
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/ */
open func prepare() { @objc
contentScaleFactor = Screen.scale fileprivate func handleKeyboardDidShow(notification: Notification) {
textContainerInset = .zero guard isKeyboardHidden else {
backgroundColor = .white return
clipsToBounds = false
removeNotificationHandlers()
prepareNotificationHandlers()
reload()
}
/// prepares the placeholderLabel property.
fileprivate func preparePlaceholderLabel() {
if let v: UILabel = placeholderLabel {
v.font = font
v.textAlignment = textAlignment
v.numberOfLines = 0
v.backgroundColor = .clear
addSubview(v)
reload()
handleTextViewTextDidChange()
}
} }
/// Prepares the titleLabel property. isKeyboardHidden = false
fileprivate func prepareTitleLabel() {
if let v: UILabel = titleLabel {
v.isHidden = true
addSubview(v)
guard let t = text, 0 == t.utf16.count else { guard let v = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
v.alpha = 0
return return
} }
showTitleLabel() (delegate as? TextViewDelegate)?.textView?(textView: self, didShowKeyboard: v)
} }
/**
Handler for when the keyboard will close.
- Parameter notification: A Notification.
*/
@objc
fileprivate func handleKeyboardWillHide(notification: Notification) {
guard !isKeyboardHidden else {
return
} }
/// Shows and animates the titleLabel property. guard let v = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
fileprivate func showTitleLabel() { return
if let v: UILabel = titleLabel {
if v.isHidden {
if let s: String = placeholderLabel?.text {
v.text = s
} }
let h: CGFloat = ceil(v.font.lineHeight)
v.frame = CGRect(x: 0, y: -h, width: bounds.width, height: h) (delegate as? TextViewDelegate)?.textView?(textView: self, willHideKeyboard: v)
v.isHidden = false
UIView.animate(withDuration: 0.25, animations: { [weak self] in
if let s: TextView = self {
v.alpha = 1
v.frame.origin.y = -v.frame.height - s.titleLabelAnimationDistance
} }
})
/**
Handler for when the keyboard did close.
- Parameter notification: A Notification.
*/
@objc
fileprivate func handleKeyboardDidHide(notification: Notification) {
guard !isKeyboardHidden else {
return
} }
isKeyboardHidden = true
guard let v = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
return
} }
(delegate as? TextViewDelegate)?.textView?(textView: self, didHideKeyboard: v)
} }
/// Hides and animates the titleLabel property. /// Notification handler for when text editing began.
fileprivate func hideTitleLabel() { @objc
if let v: UILabel = titleLabel { fileprivate func handleTextViewTextDidBegin() {
if !v.isHidden { isEditing = true
UIView.animate(withDuration: 0.25, animations: {
v.alpha = 0
v.frame.origin.y = -v.frame.height
}) { _ in
v.isHidden = true
} }
/// Notification handler for when text changed.
@objc
fileprivate func handleTextViewTextDidChange() {
updatePlaceholderVisibility()
} }
/// Notification handler for when text editing ended.
@objc
fileprivate func handleTextViewTextDidEnd() {
isEditing = false
updatePlaceholderVisibility()
} }
}
extension TextView: TextStorageDelegate {
@objc
open func textStorage(textStorage: TextStorage, willProcessEditing text: String, range: NSRange) {
(delegate as? TextViewDelegate)?.textView?(textView: self, willProcessEditing: textStorage, text: string, range: range)
} }
/// Prepares the Notification handlers. @objc
fileprivate func prepareNotificationHandlers() { open func textStorage(textStorage: TextStorage, didProcessEditing text: String, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) {
let defaultCenter = NotificationCenter.default guard let range = result?.range else {
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidBegin), name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self) return
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidEnd), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
} }
/// Removes the Notification handlers. (delegate as? TextViewDelegate)?.textView?(textView: self, didProcessEditing: textStorage, text: string, range: range)
fileprivate func removeNotificationHandlers() {
let defaultCenter = NotificationCenter.default
defaultCenter.removeObserver(self, name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self)
defaultCenter.removeObserver(self, name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.removeObserver(self, name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment