2017-07-28 13:58:44 +08:00
//
// V i e w C o n t r o l l e r . s w i f t
// F l y c u t - i O S
//
// C r e a t e d b y M a r k J e r d e o n 7 / 1 2 / 1 7 .
//
//
import UIKit
2017-10-20 04:01:44 +08:00
class ViewController : UIViewController , UITableViewDelegate , UITableViewDataSource , FlycutStoreDelegate , FlycutOperatorDelegate {
2017-07-28 13:58:44 +08:00
let flycut : FlycutOperator = FlycutOperator ( )
2017-09-03 11:13:52 +08:00
var activeUpdates : Int = 0
2017-07-28 13:58:44 +08:00
var tableView : UITableView !
2017-09-03 11:13:52 +08:00
var currentAnimation = UITableViewRowAnimation . none
var pbCount : Int = - 1
let pasteboardInteractionQueue = DispatchQueue ( label : " com.Flycut.pasteboardInteractionQueue " )
2017-10-20 04:01:44 +08:00
let alertHandlingQueue = DispatchQueue ( label : " com.Flycut.alertHandlingQueue " )
2017-07-28 13:58:44 +08:00
2017-09-03 11:01:03 +08:00
// S o m e b u t t o n s w e w i l l r e u s e .
var deleteButton : MGSwipeButton ? = nil
var openURLButton : MGSwipeButton ? = nil
2017-07-28 13:58:44 +08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
// D o a n y a d d i t i o n a l s e t u p a f t e r l o a d i n g t h e v i e w , t y p i c a l l y f r o m a n i b .
tableView = self . view . subviews . first as ! UITableView
tableView . delegate = self
tableView . dataSource = self
2017-09-03 11:01:03 +08:00
tableView . register ( MGSwipeTableCell . self , forCellReuseIdentifier : " FlycutCell " )
deleteButton = MGSwipeButton ( title : " Delete " , backgroundColor : . red , callback : { ( cell ) -> Bool in
let indexPath = self . tableView . indexPath ( for : cell )
if ( nil != indexPath ) {
2017-09-03 11:13:52 +08:00
let previousAnimation = self . currentAnimation
self . currentAnimation = UITableViewRowAnimation . left // U s e . l e f t t o l o o k b e t t e r w i t h s w i p i n g l e f t t o d e l e t e .
2017-09-03 11:01:03 +08:00
self . flycut . setStackPositionTo ( Int32 ( ( indexPath ? . row ) ! ) )
self . flycut . clearItemAtStackPosition ( )
2017-09-03 11:13:52 +08:00
self . currentAnimation = previousAnimation
2017-09-03 11:01:03 +08:00
}
return true ;
} )
openURLButton = MGSwipeButton ( title : " Open " , backgroundColor : . blue , callback : { ( cell ) -> Bool in
let indexPath = self . tableView . indexPath ( for : cell )
if ( nil != indexPath ) {
let url = URL ( string : self . flycut . clippingString ( withCount : Int32 ( ( indexPath ? . row ) ! ) ) ! )
UIApplication . shared . open ( url ! , options : [ : ] , completionHandler : nil )
self . tableView . reloadRows ( at : [ indexPath ! ] , with : UITableViewRowAnimation . none )
}
return true ;
} )
2017-10-20 04:01:44 +08:00
// F o r c e s y n c d i s a b l e f o r t e s t i f n e e d e d .
// U s e r D e f a u l t s . s t a n d a r d . s e t ( N S N u m b e r ( v a l u e : f a l s e ) , f o r K e y : " s y n c S e t t i n g s V i a I C l o u d " )
// U s e r D e f a u l t s . s t a n d a r d . s e t ( N S N u m b e r ( v a l u e : f a l s e ) , f o r K e y : " s y n c C l i p p i n g s V i a I C l o u d " )
// F o r c e t o a s k t o e n a b l e s y n c f o r t e s t i f n e e d e d .
// U s e r D e f a u l t s . s t a n d a r d . s e t ( f a l s e , f o r K e y : " a l r e a d y A s k e d T o E n a b l e S y n c " )
2017-09-03 11:13:52 +08:00
2017-10-20 04:27:21 +08:00
// E n s u r e t h e s e a r e f a l s e s i n c e t h e r e i s n ' t a w a y t o a c c e s s t h e s a v e d c l i p p i n g s o n i O S a s t h i s p o i n t .
UserDefaults . standard . set ( NSNumber ( value : false ) , forKey : " saveForgottenClippings " )
UserDefaults . standard . set ( NSNumber ( value : false ) , forKey : " saveForgottenFavorites " )
2017-10-20 04:01:44 +08:00
flycut . setClippingsStoreDelegate ( self )
flycut . delegate = self
2017-09-03 11:13:52 +08:00
2017-08-27 12:23:20 +08:00
flycut . awake ( fromNibDisplaying : 10 , withDisplayLength : 140 , withSave : #selector ( savePreferences ( toDict : ) ) , forTarget : self ) // T h e 1 0 i s n ' t u s e d i n i O S r i g h t n o w a n d 1 4 0 c h a r a c t e r s s e e m s t o b e e n o u g h t o c o v e r t h e w i d t h o f t h e l a r g e s t s c r e e n .
2017-07-28 13:58:44 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( self . checkForClippingAddedToClipboard ) , name : . UIPasteboardChanged , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( self . applicationWillTerminate ) , name : . UIApplicationWillTerminate , object : nil )
}
2017-10-20 04:01:44 +08:00
override func viewDidAppear ( _ animated : Bool ) {
// A s k o n c e t o e n a b l e S y n c . T h e s y n t a x b e l o w w i l l t a k e t h e e l s e u n l e s s a l r e a d y A n s w e r e d i s n o n - n i l a n d t r u e .
let alreadyAsked = UserDefaults . standard . value ( forKey : " alreadyAskedToEnableSync " )
if let answer = alreadyAsked , answer as ! Bool
{
}
else
{
// D o n ' t u s e D i s p a t c h Q u e u e . m a i n . a s y n c s i n c e t h a t w i l l s t i l l e n d u p b l o c k i n g t h e U I d r a w u n t i l t h e u s e r r e s p o n d s t o w h a t h a s n ' t b e e n d r a w n y e t . J u s t c r e a t e a q u e u e t o g e t u s a w a y f r o m m a i n , s i n c e t h i s i s a o n e - t i m e c o d e p a t h .
DispatchQueue ( label : " com.Flycut.alertHandlingQueue " , qos : . userInitiated ) . async {
let selection = self . alert ( withMessageText : " iCloud Sync " , informationText : " Would you like to enable Flycut's iCloud Sync for Settings and Clippings? " , buttonsTexts : [ " Yes " , " No " ] )
let response = ( selection = = " Yes " ) ;
UserDefaults . standard . set ( NSNumber ( value : response ) , forKey : " syncSettingsViaICloud " )
UserDefaults . standard . set ( NSNumber ( value : response ) , forKey : " syncClippingsViaICloud " )
UserDefaults . standard . set ( true , forKey : " alreadyAskedToEnableSync " )
self . flycut . registerOrDeregisterICloudSync ( )
}
}
}
2017-08-27 12:23:20 +08:00
func savePreferences ( toDict : NSMutableDictionary )
{
}
2017-09-03 11:13:52 +08:00
func beginUpdates ( )
{
if ( ! Thread . isMainThread )
{
DispatchQueue . main . sync { beginUpdates ( ) }
return
}
print ( " Begin updates " )
print ( " Num rows: \( tableView . dataSource ? . tableView ( tableView , numberOfRowsInSection : 0 ) ) " )
if ( 0 = = activeUpdates )
{
tableView . beginUpdates ( )
}
activeUpdates += 1
}
func endUpdates ( )
2017-07-28 13:58:44 +08:00
{
2017-09-03 11:13:52 +08:00
if ( ! Thread . isMainThread )
{
DispatchQueue . main . sync { endUpdates ( ) }
return
}
print ( " End updates " ) ;
activeUpdates -= 1 ;
if ( 0 = = activeUpdates )
{
tableView . endUpdates ( )
}
}
func insertClipping ( at index : Int32 ) {
if ( ! Thread . isMainThread )
{
DispatchQueue . main . sync { insertClipping ( at : index ) }
return
}
print ( " Insert row \( index ) " )
tableView . insertRows ( at : [ IndexPath ( row : Int ( index ) , section : 0 ) ] , with : currentAnimation ) // W e w i l l o v e r r i d e t h e a n i m a t i o n f o r n o w , b e c a u s e w e a r e t h e V i e w C o n t r o l l e r a n d s h o u l d g u i d e t h e U X .
}
func deleteClipping ( at index : Int32 ) {
if ( ! Thread . isMainThread )
2017-07-28 13:58:44 +08:00
{
2017-09-03 11:13:52 +08:00
DispatchQueue . main . sync { deleteClipping ( at : index ) }
return
}
print ( " Delete row \( index ) " )
tableView . deleteRows ( at : [ IndexPath ( row : Int ( index ) , section : 0 ) ] , with : currentAnimation ) // W e w i l l o v e r r i d e t h e a n i m a t i o n f o r n o w , b e c a u s e w e a r e t h e V i e w C o n t r o l l e r a n d s h o u l d g u i d e t h e U X .
}
func reloadClipping ( at index : Int32 ) {
if ( ! Thread . isMainThread )
{
DispatchQueue . main . sync { reloadClipping ( at : index ) }
return
}
print ( " Reloading row \( index ) " )
tableView . reloadRows ( at : [ IndexPath ( row : Int ( index ) , section : 0 ) ] , with : currentAnimation ) // W e w i l l o v e r r i d e t h e a n i m a t i o n f o r n o w , b e c a u s e w e a r e t h e V i e w C o n t r o l l e r a n d s h o u l d g u i d e t h e U X .
}
2017-07-28 13:58:44 +08:00
2017-09-03 11:13:52 +08:00
func moveClipping ( at index : Int32 , to newIndex : Int32 ) {
if ( ! Thread . isMainThread )
{
DispatchQueue . main . sync { moveClipping ( at : index , to : newIndex ) }
return
}
print ( " Moving row \( index ) to \( newIndex ) " )
tableView . moveRow ( at : IndexPath ( row : Int ( index ) , section : 0 ) , to : IndexPath ( row : Int ( newIndex ) , section : 0 ) )
}
2017-07-28 13:58:44 +08:00
2017-10-20 04:01:44 +08:00
func alert ( withMessageText message : String ! , informationText information : String ! , buttonsTexts buttons : [ Any ] ! ) -> String ! {
// D o n ' t u s e D i s p a t c h Q u e u e . m a i n . a s y n c s i n c e t h a t w i l l s t i l l e n d u p b l o c k i n g t h e U I d r a w u n t i l t h e u s e r r e s p o n d s t o w h a t h a s n ' t b e e n d r a w n y e t . T h i s i s n ' t a g r e a t c h e c k , a s i t i s O S - v e r s i o n - l i m i t e d a n d r e s u l t s i n a E X C _ B A D _ I N S T R U C T I O N i f i t f a i l s , b u t i s g o o d e n o u g h f o r d e v e l o p m e n t / t e s t .
if #available ( iOS 10.0 , * ) {
__dispatch_assert_queue_not ( DispatchQueue . main )
}
let alertController = UIAlertController ( title : message , message : information , preferredStyle : . alert )
var selection : String ? = nil
for option in buttons
{
alertController . addAction ( UIAlertAction ( title : option as ? String , style : . default ) { action in
selection = action . title
self . alertHandlingQueue . resume ( )
} )
}
// T r a n s f o r m t h e a s y n c h r o n o u s U I A l e r t C o n t r o l l e r i n t o a s y n c h r o n o u s a l e r t b y s u s p e n d i n g a G C D s e r i a l q u e u e b e f o r e p r e s e n t i n g t h e n p l a c i n g a n e m p t y s y n c o n t h a t q u e u e t o b l o c k u n t i l i t i s r e s u m e d , a n d r e s u m i n g a f t e r s e l e c t i o n . T h e G C D s y n c c a n ' t c o m p l e t e u n t i l t h e s e l e c t i o n r e s u m e s t h e q u e u e .
alertHandlingQueue . suspend ( )
self . present ( alertController , animated : true )
alertHandlingQueue . sync { } // T o w a i t f o r q u e u e t o r e s u m e .
return selection
}
2017-09-03 11:13:52 +08:00
func checkForClippingAddedToClipboard ( )
{
pasteboardInteractionQueue . async {
if ( UIPasteboard . general . changeCount != self . pbCount )
2017-07-28 13:58:44 +08:00
{
2017-09-03 11:13:52 +08:00
self . pbCount = UIPasteboard . general . changeCount ;
2017-07-28 13:58:44 +08:00
2017-09-03 11:13:52 +08:00
if ( UIPasteboard . general . types . contains ( " public.utf8-plain-text " ) )
2017-07-28 13:58:44 +08:00
{
2017-09-03 11:13:52 +08:00
let pasteboard = UIPasteboard . general . value ( forPasteboardType : " public.utf8-plain-text " )
self . flycut . addClipping ( pasteboard as ! String ! , ofType : " public.utf8-plain-text " , fromApp : " iOS " , withAppBundleURL : " iOS " , target : nil , clippingAddedSelector : nil )
2017-07-28 13:58:44 +08:00
}
2017-10-20 04:02:43 +08:00
else if ( UIPasteboard . general . types . contains ( " public.text " ) )
{
let pasteboard = UIPasteboard . general . value ( forPasteboardType : " public.text " )
self . flycut . addClipping ( pasteboard as ! String ! , ofType : " public.text " , fromApp : " iOS " , withAppBundleURL : " iOS " , target : nil , clippingAddedSelector : nil )
}
2017-07-28 13:58:44 +08:00
}
}
}
func applicationWillTerminate ( )
{
saveEngine ( )
}
func saveEngine ( )
{
flycut . saveEngine ( )
}
override func didReceiveMemoryWarning ( ) {
super . didReceiveMemoryWarning ( )
saveEngine ( )
// D i s p o s e o f a n y r e s o u r c e s t h a t c a n b e r e c r e a t e d .
}
func numberOfSections ( in tableView : UITableView ) -> Int {
return 1
}
func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
2017-09-03 11:13:52 +08:00
return Int ( flycut . jcListCount ( ) )
2017-07-28 13:58:44 +08:00
}
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
2017-09-03 11:01:03 +08:00
let item : MGSwipeTableCell = tableView . dequeueReusableCell ( withIdentifier : " FlycutCell " , for : indexPath ) as ! MGSwipeTableCell
2017-07-28 13:58:44 +08:00
item . textLabel ? . text = flycut . previousDisplayStrings ( indexPath . row + 1 , containing : nil ) . last as ! String ?
let content = flycut . clippingString ( withCount : Int32 ( indexPath . row ) )
2017-08-15 12:18:50 +08:00
// c o n f i g u r e l e f t b u t t o n s
2017-09-03 11:01:03 +08:00
if URL ( string : content ! ) != nil {
2017-08-15 12:18:50 +08:00
if ( content ? . lowercased ( ) . hasPrefix ( " http " ) ) ! {
2017-09-03 11:01:03 +08:00
if ( ! item . leftButtons . contains ( openURLButton ! ) )
{
item . leftButtons . append ( openURLButton ! )
item . leftSwipeSettings . transition = . border
item . leftExpansion . buttonIndex = 0
}
2017-07-28 13:58:44 +08:00
}
2017-09-03 11:01:03 +08:00
else {
item . leftButtons . removeAll ( )
}
}
else {
item . leftButtons . removeAll ( )
2017-07-28 13:58:44 +08:00
}
2017-08-15 12:18:50 +08:00
// c o n f i g u r e r i g h t b u t t o n s
2017-09-03 11:01:03 +08:00
if ( 0 = = item . rightButtons . count )
{
// S e t u p t h e r i g h t b u t t o n s o n l y i f t h e y h a v e n ' t b e e n b e f o r e .
item . rightButtons . append ( deleteButton ! )
item . rightSwipeSettings . transition = . border
item . rightExpansion . buttonIndex = 0
}
2017-08-15 12:18:50 +08:00
return item
}
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
if ( MGSwipeState . none = = ( tableView . cellForRow ( at : indexPath ) as ! MGSwipeTableCell ) . swipeState ) {
2017-09-03 11:13:52 +08:00
tableView . deselectRow ( at : indexPath , animated : true ) // d e s e l e c t b e f o r e g e t P a s t e s i n c e g e t P a s t e m a y r e o r d e r t h e l i s t
let content = flycut . getPasteFrom ( Int32 ( indexPath . row ) )
2017-08-15 12:18:50 +08:00
print ( " Select: \( indexPath . row ) \( content ) OK " )
2017-09-03 11:13:52 +08:00
pasteboardInteractionQueue . async {
// C a p t u r e v a l u e b e f o r e s e t t i n g t h e p a s t b o a r d f o r r e a s o n s n o t e d b e l o w .
self . pbCount = UIPasteboard . general . changeCount
// T h i s c a l l w i l l c l e a r a l l o t h e r c o n t e n t t y p e s a n d a p p e a r s t o i m m e d i a t e l y i n c r e m e n t t h e c h a n g e C o u n t .
UIPasteboard . general . setValue ( content as Any , forPasteboardType : " public.utf8-plain-text " )
// A p p l e d o c u m e n t s t h a t " U I P a s t e b o a r d w a i t s u n t i l t h e e n d o f t h e c u r r e n t e v e n t l o o p b e f o r e i n c r e m e n t i n g t h e c h a n g e c o u n t " , b u t t h i s d o e s n ' t s e e m t o b e t h e c a s e f o r t h e a b o v e c a l l . H a n d l e b o t h s c e n a r i o s b y d o i n g a s i m p l e i n c r e m e n t i f u n c h a n g e d a n d a n u p d a t e - t o - m a t c h i f c h a n g e d .
if ( UIPasteboard . general . changeCount = = self . pbCount )
{
self . pbCount += 1
}
else
{
self . pbCount = UIPasteboard . general . changeCount
}
}
2017-08-15 12:18:50 +08:00
}
2017-07-28 13:58:44 +08:00
}
}