├── .gitignore ├── Demo ├── DemoStoryboard.storyboard ├── JCNotificationBannerPresenterDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ └── jcoleman.xcuserdatad │ │ └── xcschemes │ │ ├── JCNotificationBannerPresenterDemo.xcscheme │ │ └── xcschememanagement.plist └── JCNotificationBannerPresenterDemo │ ├── Default-568h@2x.png │ ├── Default.png │ ├── Default@2x.png │ ├── JCAppDelegate.h │ ├── JCAppDelegate.m │ ├── JCNotificationBannerPresenterDemo-Info.plist │ ├── JCNotificationBannerPresenterDemo-Prefix.pch │ ├── JCNotificationPresenterDemoViewController.h │ ├── JCNotificationPresenterDemoViewController.m │ ├── en.lproj │ └── InfoPlist.strings │ └── main.m ├── JCNotificationBannerPresenter.podspec ├── LICENSE.txt ├── Library ├── JCNotificationBanner.h ├── JCNotificationBanner.m ├── JCNotificationBannerPresenter.h ├── JCNotificationBannerPresenter.m ├── JCNotificationBannerPresenterIOS7Style.h ├── JCNotificationBannerPresenterIOS7Style.m ├── JCNotificationBannerPresenterIOSStyle.h ├── JCNotificationBannerPresenterIOSStyle.m ├── JCNotificationBannerPresenterSmokeStyle.h ├── JCNotificationBannerPresenterSmokeStyle.m ├── JCNotificationBannerPresenter_Private.h ├── JCNotificationBannerView.h ├── JCNotificationBannerView.m ├── JCNotificationBannerViewController.h ├── JCNotificationBannerViewController.m ├── JCNotificationBannerViewIOS7Style.h ├── JCNotificationBannerViewIOS7Style.m ├── JCNotificationBannerViewIOSStyle.h ├── JCNotificationBannerViewIOSStyle.m ├── JCNotificationBannerWindow.h ├── JCNotificationBannerWindow.m ├── JCNotificationCenter.h └── JCNotificationCenter.m └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /Demo/DemoStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 53B55DE016CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B55DDF16CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.m */; }; 11 | 53B55DE416CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B55DE316CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.m */; }; 12 | 53B55DE716CD5A880011222B /* JCNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B55DE616CD5A880011222B /* JCNotificationCenter.m */; }; 13 | 6D0CCF3716AF604200A28EB5 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D0CCF3616AF604200A28EB5 /* QuartzCore.framework */; }; 14 | C40C9695187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.m in Sources */ = {isa = PBXBuildFile; fileRef = C40C9694187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.m */; }; 15 | C40C9698187A471400CC235F /* JCNotificationBannerViewIOS7Style.m in Sources */ = {isa = PBXBuildFile; fileRef = C40C9697187A471400CC235F /* JCNotificationBannerViewIOS7Style.m */; }; 16 | C41E33061640322C0049334F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C41E33051640322C0049334F /* UIKit.framework */; }; 17 | C41E33081640322C0049334F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C41E33071640322C0049334F /* Foundation.framework */; }; 18 | C41E330A1640322C0049334F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C41E33091640322C0049334F /* CoreGraphics.framework */; }; 19 | C41E33101640322C0049334F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C41E330E1640322C0049334F /* InfoPlist.strings */; }; 20 | C41E33121640322C0049334F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E33111640322C0049334F /* main.m */; }; 21 | C41E33161640322C0049334F /* JCAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E33151640322C0049334F /* JCAppDelegate.m */; }; 22 | C41E33181640322C0049334F /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = C41E33171640322C0049334F /* Default.png */; }; 23 | C41E331A1640322C0049334F /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C41E33191640322C0049334F /* Default@2x.png */; }; 24 | C41E331C1640322C0049334F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C41E331B1640322C0049334F /* Default-568h@2x.png */; }; 25 | C41E332E1640354D0049334F /* DemoStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41E332D1640354D0049334F /* DemoStoryboard.storyboard */; }; 26 | C41E3331164035F70049334F /* JCNotificationPresenterDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E3330164035F70049334F /* JCNotificationPresenterDemoViewController.m */; }; 27 | C41E333E164036F20049334F /* JCNotificationBanner.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E3335164036F20049334F /* JCNotificationBanner.m */; }; 28 | C41E333F164036F20049334F /* JCNotificationBannerPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E3337164036F20049334F /* JCNotificationBannerPresenter.m */; }; 29 | C41E3340164036F20049334F /* JCNotificationBannerView.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E3339164036F20049334F /* JCNotificationBannerView.m */; }; 30 | C41E3341164036F20049334F /* JCNotificationBannerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E333B164036F20049334F /* JCNotificationBannerViewController.m */; }; 31 | C41E3342164036F20049334F /* JCNotificationBannerWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C41E333D164036F20049334F /* JCNotificationBannerWindow.m */; }; 32 | C49AA7F516CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = C49AA7F416CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 53B55DDD16CD594E0011222B /* JCNotificationBannerPresenter_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerPresenter_Private.h; sourceTree = ""; }; 37 | 53B55DDE16CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerPresenterSmokeStyle.h; sourceTree = ""; }; 38 | 53B55DDF16CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerPresenterSmokeStyle.m; sourceTree = ""; }; 39 | 53B55DE216CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerPresenterIOSStyle.h; sourceTree = ""; }; 40 | 53B55DE316CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerPresenterIOSStyle.m; sourceTree = ""; }; 41 | 53B55DE516CD5A880011222B /* JCNotificationCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationCenter.h; sourceTree = ""; }; 42 | 53B55DE616CD5A880011222B /* JCNotificationCenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationCenter.m; sourceTree = ""; }; 43 | 6D0CCF3616AF604200A28EB5 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 44 | C40C9693187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerPresenterIOS7Style.h; sourceTree = ""; }; 45 | C40C9694187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerPresenterIOS7Style.m; sourceTree = ""; }; 46 | C40C9696187A471400CC235F /* JCNotificationBannerViewIOS7Style.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerViewIOS7Style.h; sourceTree = ""; }; 47 | C40C9697187A471400CC235F /* JCNotificationBannerViewIOS7Style.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerViewIOS7Style.m; sourceTree = ""; }; 48 | C41E33011640322C0049334F /* JCNotificationBannerPresenterDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JCNotificationBannerPresenterDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | C41E33051640322C0049334F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 50 | C41E33071640322C0049334F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 51 | C41E33091640322C0049334F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 52 | C41E330D1640322C0049334F /* JCNotificationBannerPresenterDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JCNotificationBannerPresenterDemo-Info.plist"; sourceTree = ""; }; 53 | C41E330F1640322C0049334F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 54 | C41E33111640322C0049334F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | C41E33131640322C0049334F /* JCNotificationBannerPresenterDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JCNotificationBannerPresenterDemo-Prefix.pch"; sourceTree = ""; }; 56 | C41E33141640322C0049334F /* JCAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JCAppDelegate.h; sourceTree = ""; }; 57 | C41E33151640322C0049334F /* JCAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JCAppDelegate.m; sourceTree = ""; }; 58 | C41E33171640322C0049334F /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 59 | C41E33191640322C0049334F /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 60 | C41E331B1640322C0049334F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 61 | C41E332D1640354D0049334F /* DemoStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = DemoStoryboard.storyboard; path = ../DemoStoryboard.storyboard; sourceTree = ""; }; 62 | C41E332F164035F70049334F /* JCNotificationPresenterDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationPresenterDemoViewController.h; sourceTree = ""; }; 63 | C41E3330164035F70049334F /* JCNotificationPresenterDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationPresenterDemoViewController.m; sourceTree = ""; }; 64 | C41E3334164036F20049334F /* JCNotificationBanner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBanner.h; sourceTree = ""; }; 65 | C41E3335164036F20049334F /* JCNotificationBanner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBanner.m; sourceTree = ""; }; 66 | C41E3336164036F20049334F /* JCNotificationBannerPresenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerPresenter.h; sourceTree = ""; }; 67 | C41E3337164036F20049334F /* JCNotificationBannerPresenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerPresenter.m; sourceTree = ""; }; 68 | C41E3338164036F20049334F /* JCNotificationBannerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerView.h; sourceTree = ""; }; 69 | C41E3339164036F20049334F /* JCNotificationBannerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerView.m; sourceTree = ""; }; 70 | C41E333A164036F20049334F /* JCNotificationBannerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerViewController.h; sourceTree = ""; }; 71 | C41E333B164036F20049334F /* JCNotificationBannerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerViewController.m; sourceTree = ""; }; 72 | C41E333C164036F20049334F /* JCNotificationBannerWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerWindow.h; sourceTree = ""; }; 73 | C41E333D164036F20049334F /* JCNotificationBannerWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerWindow.m; sourceTree = ""; }; 74 | C49AA7F316CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JCNotificationBannerViewIOSStyle.h; sourceTree = ""; }; 75 | C49AA7F416CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JCNotificationBannerViewIOSStyle.m; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | C41E32FE1640322C0049334F /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 6D0CCF3716AF604200A28EB5 /* QuartzCore.framework in Frameworks */, 84 | C41E33061640322C0049334F /* UIKit.framework in Frameworks */, 85 | C41E33081640322C0049334F /* Foundation.framework in Frameworks */, 86 | C41E330A1640322C0049334F /* CoreGraphics.framework in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | C41E32F61640322C0049334F = { 94 | isa = PBXGroup; 95 | children = ( 96 | C41E330B1640322C0049334F /* JCNotificationBannerPresenterDemo */, 97 | C41E33041640322C0049334F /* Frameworks */, 98 | C41E33021640322C0049334F /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | C41E33021640322C0049334F /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | C41E33011640322C0049334F /* JCNotificationBannerPresenterDemo.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | C41E33041640322C0049334F /* Frameworks */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 6D0CCF3616AF604200A28EB5 /* QuartzCore.framework */, 114 | C41E33051640322C0049334F /* UIKit.framework */, 115 | C41E33071640322C0049334F /* Foundation.framework */, 116 | C41E33091640322C0049334F /* CoreGraphics.framework */, 117 | ); 118 | name = Frameworks; 119 | sourceTree = ""; 120 | }; 121 | C41E330B1640322C0049334F /* JCNotificationBannerPresenterDemo */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | C41E3333164036F20049334F /* Library */, 125 | C41E33141640322C0049334F /* JCAppDelegate.h */, 126 | C41E33151640322C0049334F /* JCAppDelegate.m */, 127 | C41E332D1640354D0049334F /* DemoStoryboard.storyboard */, 128 | C41E332F164035F70049334F /* JCNotificationPresenterDemoViewController.h */, 129 | C41E3330164035F70049334F /* JCNotificationPresenterDemoViewController.m */, 130 | C41E330C1640322C0049334F /* Supporting Files */, 131 | ); 132 | path = JCNotificationBannerPresenterDemo; 133 | sourceTree = ""; 134 | }; 135 | C41E330C1640322C0049334F /* Supporting Files */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | C41E330D1640322C0049334F /* JCNotificationBannerPresenterDemo-Info.plist */, 139 | C41E330E1640322C0049334F /* InfoPlist.strings */, 140 | C41E33111640322C0049334F /* main.m */, 141 | C41E33131640322C0049334F /* JCNotificationBannerPresenterDemo-Prefix.pch */, 142 | C41E33171640322C0049334F /* Default.png */, 143 | C41E33191640322C0049334F /* Default@2x.png */, 144 | C41E331B1640322C0049334F /* Default-568h@2x.png */, 145 | ); 146 | name = "Supporting Files"; 147 | sourceTree = ""; 148 | }; 149 | C41E3333164036F20049334F /* Library */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 53B55DE216CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.h */, 153 | 53B55DE316CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.m */, 154 | C40C9693187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.h */, 155 | C40C9694187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.m */, 156 | 53B55DDD16CD594E0011222B /* JCNotificationBannerPresenter_Private.h */, 157 | 53B55DDE16CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.h */, 158 | 53B55DDF16CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.m */, 159 | C41E3334164036F20049334F /* JCNotificationBanner.h */, 160 | C41E3335164036F20049334F /* JCNotificationBanner.m */, 161 | C41E3336164036F20049334F /* JCNotificationBannerPresenter.h */, 162 | C41E3337164036F20049334F /* JCNotificationBannerPresenter.m */, 163 | C41E3338164036F20049334F /* JCNotificationBannerView.h */, 164 | C41E3339164036F20049334F /* JCNotificationBannerView.m */, 165 | C49AA7F316CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.h */, 166 | C49AA7F416CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.m */, 167 | C40C9696187A471400CC235F /* JCNotificationBannerViewIOS7Style.h */, 168 | C40C9697187A471400CC235F /* JCNotificationBannerViewIOS7Style.m */, 169 | C41E333A164036F20049334F /* JCNotificationBannerViewController.h */, 170 | C41E333B164036F20049334F /* JCNotificationBannerViewController.m */, 171 | C41E333C164036F20049334F /* JCNotificationBannerWindow.h */, 172 | C41E333D164036F20049334F /* JCNotificationBannerWindow.m */, 173 | 53B55DE516CD5A880011222B /* JCNotificationCenter.h */, 174 | 53B55DE616CD5A880011222B /* JCNotificationCenter.m */, 175 | ); 176 | name = Library; 177 | path = ../../Library; 178 | sourceTree = ""; 179 | }; 180 | /* End PBXGroup section */ 181 | 182 | /* Begin PBXNativeTarget section */ 183 | C41E33001640322C0049334F /* JCNotificationBannerPresenterDemo */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = C41E331F1640322C0049334F /* Build configuration list for PBXNativeTarget "JCNotificationBannerPresenterDemo" */; 186 | buildPhases = ( 187 | C41E32FD1640322C0049334F /* Sources */, 188 | C41E32FE1640322C0049334F /* Frameworks */, 189 | C41E32FF1640322C0049334F /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = JCNotificationBannerPresenterDemo; 196 | productName = JCNotificationBannerPresenterDemo; 197 | productReference = C41E33011640322C0049334F /* JCNotificationBannerPresenterDemo.app */; 198 | productType = "com.apple.product-type.application"; 199 | }; 200 | /* End PBXNativeTarget section */ 201 | 202 | /* Begin PBXProject section */ 203 | C41E32F81640322C0049334F /* Project object */ = { 204 | isa = PBXProject; 205 | attributes = { 206 | CLASSPREFIX = JC; 207 | LastUpgradeCheck = 0450; 208 | ORGANIZATIONNAME = "James Coleman"; 209 | }; 210 | buildConfigurationList = C41E32FB1640322C0049334F /* Build configuration list for PBXProject "JCNotificationBannerPresenterDemo" */; 211 | compatibilityVersion = "Xcode 3.2"; 212 | developmentRegion = English; 213 | hasScannedForEncodings = 0; 214 | knownRegions = ( 215 | en, 216 | ); 217 | mainGroup = C41E32F61640322C0049334F; 218 | productRefGroup = C41E33021640322C0049334F /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | C41E33001640322C0049334F /* JCNotificationBannerPresenterDemo */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | C41E32FF1640322C0049334F /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | C41E33101640322C0049334F /* InfoPlist.strings in Resources */, 233 | C41E33181640322C0049334F /* Default.png in Resources */, 234 | C41E331A1640322C0049334F /* Default@2x.png in Resources */, 235 | C41E331C1640322C0049334F /* Default-568h@2x.png in Resources */, 236 | C41E332E1640354D0049334F /* DemoStoryboard.storyboard in Resources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXResourcesBuildPhase section */ 241 | 242 | /* Begin PBXSourcesBuildPhase section */ 243 | C41E32FD1640322C0049334F /* Sources */ = { 244 | isa = PBXSourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | C40C9698187A471400CC235F /* JCNotificationBannerViewIOS7Style.m in Sources */, 248 | C41E33121640322C0049334F /* main.m in Sources */, 249 | C41E33161640322C0049334F /* JCAppDelegate.m in Sources */, 250 | C41E3331164035F70049334F /* JCNotificationPresenterDemoViewController.m in Sources */, 251 | C41E333E164036F20049334F /* JCNotificationBanner.m in Sources */, 252 | C41E333F164036F20049334F /* JCNotificationBannerPresenter.m in Sources */, 253 | C40C9695187A45B600CC235F /* JCNotificationBannerPresenterIOS7Style.m in Sources */, 254 | C41E3340164036F20049334F /* JCNotificationBannerView.m in Sources */, 255 | C41E3341164036F20049334F /* JCNotificationBannerViewController.m in Sources */, 256 | C41E3342164036F20049334F /* JCNotificationBannerWindow.m in Sources */, 257 | C49AA7F516CC40FF0079EDAE /* JCNotificationBannerViewIOSStyle.m in Sources */, 258 | 53B55DE016CD594E0011222B /* JCNotificationBannerPresenterSmokeStyle.m in Sources */, 259 | 53B55DE416CD596D0011222B /* JCNotificationBannerPresenterIOSStyle.m in Sources */, 260 | 53B55DE716CD5A880011222B /* JCNotificationCenter.m in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXSourcesBuildPhase section */ 265 | 266 | /* Begin PBXVariantGroup section */ 267 | C41E330E1640322C0049334F /* InfoPlist.strings */ = { 268 | isa = PBXVariantGroup; 269 | children = ( 270 | C41E330F1640322C0049334F /* en */, 271 | ); 272 | name = InfoPlist.strings; 273 | sourceTree = ""; 274 | }; 275 | /* End PBXVariantGroup section */ 276 | 277 | /* Begin XCBuildConfiguration section */ 278 | C41E331D1640322C0049334F /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 283 | CLANG_CXX_LIBRARY = "libc++"; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 288 | COPY_PHASE_STRIP = NO; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_DYNAMIC_NO_PIC = NO; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 297 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos6.1; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Debug; 306 | }; 307 | C41E331E1640322C0049334F /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 317 | COPY_PHASE_STRIP = YES; 318 | GCC_C_LANGUAGE_STANDARD = gnu99; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 323 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 324 | SDKROOT = iphoneos6.1; 325 | TARGETED_DEVICE_FAMILY = "1,2"; 326 | VALIDATE_PRODUCT = YES; 327 | }; 328 | name = Release; 329 | }; 330 | C41E33201640322C0049334F /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 334 | GCC_PREFIX_HEADER = "JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Prefix.pch"; 335 | INFOPLIST_FILE = "JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Info.plist"; 336 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | WRAPPER_EXTENSION = app; 339 | }; 340 | name = Debug; 341 | }; 342 | C41E33211640322C0049334F /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 346 | GCC_PREFIX_HEADER = "JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Prefix.pch"; 347 | INFOPLIST_FILE = "JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Info.plist"; 348 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 349 | PRODUCT_NAME = "$(TARGET_NAME)"; 350 | WRAPPER_EXTENSION = app; 351 | }; 352 | name = Release; 353 | }; 354 | /* End XCBuildConfiguration section */ 355 | 356 | /* Begin XCConfigurationList section */ 357 | C41E32FB1640322C0049334F /* Build configuration list for PBXProject "JCNotificationBannerPresenterDemo" */ = { 358 | isa = XCConfigurationList; 359 | buildConfigurations = ( 360 | C41E331D1640322C0049334F /* Debug */, 361 | C41E331E1640322C0049334F /* Release */, 362 | ); 363 | defaultConfigurationIsVisible = 0; 364 | defaultConfigurationName = Release; 365 | }; 366 | C41E331F1640322C0049334F /* Build configuration list for PBXNativeTarget "JCNotificationBannerPresenterDemo" */ = { 367 | isa = XCConfigurationList; 368 | buildConfigurations = ( 369 | C41E33201640322C0049334F /* Debug */, 370 | C41E33211640322C0049334F /* Release */, 371 | ); 372 | defaultConfigurationIsVisible = 0; 373 | defaultConfigurationName = Release; 374 | }; 375 | /* End XCConfigurationList section */ 376 | }; 377 | rootObject = C41E32F81640322C0049334F /* Project object */; 378 | } 379 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo.xcodeproj/xcuserdata/jcoleman.xcuserdatad/xcschemes/JCNotificationBannerPresenterDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo.xcodeproj/xcuserdata/jcoleman.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JCNotificationBannerPresenterDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C41E33001640322C0049334F 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcoleman/JCNotificationBannerPresenter/203ae740269f9b0bfa85ec9a575a3ce84b734630/Demo/JCNotificationBannerPresenterDemo/Default-568h@2x.png -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcoleman/JCNotificationBannerPresenter/203ae740269f9b0bfa85ec9a575a3ce84b734630/Demo/JCNotificationBannerPresenterDemo/Default.png -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcoleman/JCNotificationBannerPresenter/203ae740269f9b0bfa85ec9a575a3ce84b734630/Demo/JCNotificationBannerPresenterDemo/Default@2x.png -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCAppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface JCAppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow* window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCAppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "JCAppDelegate.h" 2 | #import "JCNotificationPresenterDemoViewController.h" 3 | 4 | @implementation JCAppDelegate 5 | 6 | - (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 7 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 8 | 9 | UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"DemoStoryboard" bundle:nil]; 10 | JCNotificationPresenterDemoViewController* demoController; 11 | demoController = (JCNotificationPresenterDemoViewController*)[storyboard instantiateInitialViewController]; 12 | self.window.rootViewController = demoController; 13 | 14 | self.window.backgroundColor = [UIColor whiteColor]; 15 | [self.window makeKeyAndVisible]; 16 | 17 | return YES; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.jtcoleman.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCNotificationBannerPresenterDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'JCNotificationBannerPresenterDemo' target in the 'JCNotificationBannerPresenterDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCNotificationPresenterDemoViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBannerPresenter.h" 3 | 4 | @interface JCNotificationPresenterDemoViewController : UIViewController 5 | 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/JCNotificationPresenterDemoViewController.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationPresenterDemoViewController.h" 2 | #import "JCNotificationCenter.h" 3 | #import "JCNotificationBannerPresenterSmokeStyle.h" 4 | #import "JCNotificationBannerPresenterIOSStyle.h" 5 | #import "JCNotificationBannerPresenterIOS7Style.h" 6 | 7 | @interface JCNotificationPresenterDemoViewController () 8 | 9 | @property (weak, nonatomic) IBOutlet UITextField* titleTextField; 10 | @property (weak, nonatomic) IBOutlet UITextView* messageTextView; 11 | @property (weak, nonatomic) IBOutlet UISegmentedControl* styleSwitch; 12 | 13 | @end 14 | 15 | @implementation JCNotificationPresenterDemoViewController 16 | 17 | - (IBAction) presentNotificationButtonTapped:(id)sender { 18 | switch (self.styleSwitch.selectedSegmentIndex) { 19 | case 0: 20 | [JCNotificationCenter sharedCenter].presenter = [JCNotificationBannerPresenterSmokeStyle new]; 21 | break; 22 | case 1: 23 | [JCNotificationCenter sharedCenter].presenter = [JCNotificationBannerPresenterIOSStyle new]; 24 | break; 25 | case 2: 26 | default: 27 | [JCNotificationCenter sharedCenter].presenter = [JCNotificationBannerPresenterIOS7Style new]; 28 | break; 29 | } 30 | 31 | [JCNotificationCenter 32 | enqueueNotificationWithTitle:self.titleTextField.text 33 | message:self.messageTextView.text 34 | tapHandler:^{ 35 | UIAlertView* alert = [[UIAlertView alloc] 36 | initWithTitle:@"Tapped notification" 37 | message:@"Perform some custom action on notification tap event..." 38 | delegate:nil 39 | cancelButtonTitle:@"OK" 40 | otherButtonTitles:nil]; 41 | [alert show]; 42 | }]; 43 | } 44 | 45 | - (void) viewDidUnload { 46 | [self setMessageTextView:nil]; 47 | [self setTitleTextField:nil]; 48 | [super viewDidUnload]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Demo/JCNotificationBannerPresenterDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JCNotificationBannerPresenterDemo 4 | // 5 | // Created by James Coleman on 10/30/12. 6 | // Copyright (c) 2012 James Coleman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "JCAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([JCAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /JCNotificationBannerPresenter.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'JCNotificationBannerPresenter' 3 | s.platform = :ios 4 | s.version = '1.1.2' 5 | s.summary = 'A library for generic presentation of "banners" (e.g. to present a push notification) from anywhere inside an iOS app.' 6 | s.license = { :type => 'MIT', :file => 'LICENSE.txt' } 7 | s.homepage = 'https://github.com/jcoleman/JCNotificationBannerPresenter' 8 | s.author = { 'James Coleman' => 'jtc331@gmail.com' } 9 | s.requires_arc = true 10 | s.framework = 'QuartzCore' 11 | s.source = { :git => 'https://github.com/jcoleman/JCNotificationBannerPresenter.git', :tag => s.version } 12 | s.source_files = 'Library/*.{h,m}' 13 | end 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 James Coleman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Library/JCNotificationBanner.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef void (^JCNotificationBannerTapHandlingBlock)(); 4 | 5 | @interface JCNotificationBanner : NSObject 6 | 7 | @property (nonatomic) NSString* title; 8 | @property (nonatomic) NSString* message; 9 | @property (nonatomic, assign) NSTimeInterval timeout; 10 | @property (nonatomic, copy) JCNotificationBannerTapHandlingBlock tapHandler; 11 | 12 | - (JCNotificationBanner*) initWithTitle:(NSString*)title 13 | message:(NSString*)message 14 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler; 15 | 16 | - (JCNotificationBanner*) initWithTitle:(NSString*)title 17 | message:(NSString*)message 18 | timeout:(NSTimeInterval)timeout 19 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler; 20 | @end 21 | -------------------------------------------------------------------------------- /Library/JCNotificationBanner.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBanner.h" 2 | 3 | @implementation JCNotificationBanner 4 | 5 | @synthesize title; 6 | @synthesize message; 7 | @synthesize timeout; 8 | @synthesize tapHandler; 9 | 10 | - (JCNotificationBanner*) initWithTitle:(NSString*)_title 11 | message:(NSString*)_message 12 | tapHandler:(JCNotificationBannerTapHandlingBlock)_tapHandler { 13 | 14 | return [self initWithTitle:_title message:_message timeout:5.0 tapHandler:_tapHandler]; 15 | } 16 | 17 | 18 | - (JCNotificationBanner*) initWithTitle:(NSString*)_title 19 | message:(NSString*)_message 20 | timeout:(NSTimeInterval)_timeout 21 | tapHandler:(JCNotificationBannerTapHandlingBlock)_tapHandler { 22 | 23 | self = [super init]; 24 | if (self) { 25 | self.title = _title; 26 | self.message = _message; 27 | self.timeout = _timeout; 28 | self.tapHandler = _tapHandler; 29 | } 30 | return self; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBanner.h" 3 | 4 | typedef void (^JCNotificationBannerPresenterFinishedBlock)(); 5 | 6 | @class JCNotificationBannerWindow; 7 | 8 | @interface JCNotificationBannerPresenter : NSObject 9 | 10 | - (void)willBeginPresentingNotifications; 11 | - (void)didFinishPresentingNotifications; 12 | - (void) presentNotification:(JCNotificationBanner*)notification 13 | finished:(JCNotificationBannerPresenterFinishedBlock)finished; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenter.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerPresenter.h" 2 | #import "JCNotificationBannerPresenter_Private.h" 3 | #import "JCNotificationBannerPresenterIOSStyle.h" 4 | #import "JCNotificationBannerViewIOSStyle.h" 5 | #import "JCNotificationBannerView.h" 6 | #import "JCNotificationBannerWindow.h" 7 | #import "JCNotificationBannerViewController.h" 8 | #import 9 | 10 | @implementation JCNotificationBannerPresenter 11 | 12 | // JCNotificationCenter calls this whenever a presenter 13 | // is about to be used to present one or more notifications. 14 | // It is guaranteed to be called exactly once before presentNotification:finished:. 15 | - (void) willBeginPresentingNotifications { 16 | bannerWindow = [self newWindow]; 17 | } 18 | 19 | // JCNotificationCenter calls this whenever it has finished 20 | // using a presenter to present notifications. 21 | // It is guaranteed to be called exactly once after 22 | // one or more calls to presentNotification:finished:. 23 | - (void) didFinishPresentingNotifications { 24 | bannerWindow.hidden = YES; 25 | [bannerWindow removeFromSuperview]; 26 | bannerWindow.rootViewController = nil; 27 | bannerWindow = nil; 28 | } 29 | 30 | // Override this method in your subclass if your notification 31 | // style uses a window. 32 | - (void) presentNotification:(JCNotificationBanner*)notification 33 | inWindow:(JCNotificationBannerWindow*)window 34 | finished:(JCNotificationBannerPresenterFinishedBlock)finished { 35 | // Abstract. Override this and call finished() whenever you are 36 | // done showing the notification. 37 | } 38 | 39 | // JCNotificationCenter calls this each time a notification should be presented. 40 | // You can take as long as you like, but make sure you call finished() whenever 41 | // you are ready to display the next notification, if any. 42 | // 43 | // If you do not require a window, override -willBeginPresentingNotifications, 44 | // -didFinishPresentingNotifications, and do whatever windowless thing you like. 45 | - (void) presentNotification:(JCNotificationBanner*)notification 46 | finished:(JCNotificationBannerPresenterFinishedBlock)finished { 47 | [self presentNotification:notification 48 | inWindow:bannerWindow 49 | finished:finished]; 50 | 51 | } 52 | 53 | #pragma mark - View helpers 54 | 55 | - (JCNotificationBannerWindow*) newWindow { 56 | JCNotificationBannerWindow* window = [[JCNotificationBannerWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 57 | window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 58 | window.userInteractionEnabled = YES; 59 | window.autoresizesSubviews = YES; 60 | window.opaque = NO; 61 | window.hidden = NO; 62 | return window; 63 | } 64 | 65 | - (UIView*) newContainerViewForNotification:(JCNotificationBanner*)notification { 66 | UIView* container = [UIView new]; 67 | container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 68 | container.userInteractionEnabled = YES; 69 | container.opaque = NO; 70 | return container; 71 | } 72 | 73 | - (JCNotificationBannerView*) newBannerViewForNotification:(JCNotificationBanner*)notification { 74 | JCNotificationBannerView* view = [[JCNotificationBannerView alloc] 75 | initWithNotification:notification]; 76 | view.userInteractionEnabled = YES; 77 | view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin 78 | | UIViewAutoresizingFlexibleLeftMargin 79 | | UIViewAutoresizingFlexibleRightMargin; 80 | return view; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterIOS7Style.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBannerPresenter.h" 3 | 4 | @interface JCNotificationBannerPresenterIOS7Style : JCNotificationBannerPresenter 5 | @property CGFloat bannerMaxWidth; 6 | @property CGFloat bannerHeight; 7 | @end 8 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterIOS7Style.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerPresenterIOS7Style.h" 2 | #import "JCNotificationBannerPresenter_Private.h" 3 | #import "JCNotificationBannerViewIOS7Style.h" 4 | #import "JCNotificationBannerViewController.h" 5 | 6 | @implementation JCNotificationBannerPresenterIOS7Style 7 | 8 | - (id) init { 9 | if (self = [super init]) { 10 | self.bannerMaxWidth = 350.0; 11 | self.bannerHeight = 60.0; 12 | } 13 | return self; 14 | } 15 | 16 | - (void) presentNotification:(JCNotificationBanner*)notification 17 | inWindow:(JCNotificationBannerWindow*)window 18 | finished:(JCNotificationBannerPresenterFinishedBlock)finished { 19 | JCNotificationBannerViewIOS7Style* banner = (JCNotificationBannerViewIOS7Style*)[self newBannerViewForNotification:notification]; 20 | 21 | JCNotificationBannerViewController* bannerViewController = [JCNotificationBannerViewController new]; 22 | window.rootViewController = bannerViewController; 23 | UIView* originalControllerView = bannerViewController.view; 24 | 25 | UIView* containerView = [self newContainerViewForNotification:notification]; 26 | [containerView addSubview:banner]; 27 | bannerViewController.view = containerView; 28 | 29 | window.bannerView = banner; 30 | 31 | containerView.bounds = originalControllerView.bounds; 32 | containerView.transform = originalControllerView.transform; 33 | [banner getCurrentPresentingStateAndAtomicallySetPresentingState:YES]; 34 | 35 | CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size; 36 | // Make the banner fill the width of the screen, minus any requested margins, 37 | // up to self.bannerMaxWidth. 38 | CGSize bannerSize = CGSizeMake(MIN(self.bannerMaxWidth, originalControllerView.bounds.size.width), self.bannerHeight); 39 | // Center the banner horizontally. 40 | CGFloat x = (MAX(statusBarSize.width, statusBarSize.height) / 2) - (bannerSize.width / 2); 41 | // Position the banner offscreen vertically. 42 | CGFloat y = -self.bannerHeight; 43 | 44 | #ifdef __IPHONE_7_0 45 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 46 | BOOL accountForStatusBarHeight = ([[UIDevice currentDevice].systemVersion intValue] < 7); 47 | #else 48 | // We're building for a pre-iOS7 base SDK. 49 | BOOL accountForStatusBarHeight = YES; 50 | #endif 51 | #else 52 | // We don't even have access to the iOS 7 availability constants. 53 | BOOL accountForStatusBarHeight = YES; 54 | #endif 55 | if (accountForStatusBarHeight) { 56 | y -= (MIN(statusBarSize.width, statusBarSize.height)); 57 | } 58 | 59 | banner.frame = CGRectMake(x, y, bannerSize.width, bannerSize.height); 60 | 61 | JCNotificationBannerTapHandlingBlock originalTapHandler = banner.notificationBanner.tapHandler; 62 | JCNotificationBannerTapHandlingBlock wrappingTapHandler = ^{ 63 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 64 | if (originalTapHandler) { 65 | originalTapHandler(); 66 | } 67 | 68 | [banner removeFromSuperview]; 69 | finished(); 70 | // Break the retain cycle 71 | notification.tapHandler = nil; 72 | } 73 | }; 74 | banner.notificationBanner.tapHandler = wrappingTapHandler; 75 | 76 | // Slide it down while fading it in. 77 | banner.alpha = 0; 78 | [UIView animateWithDuration:0.5 delay:0 79 | options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut 80 | animations:^{ 81 | CGRect newFrame = CGRectOffset(banner.frame, 0, banner.frame.size.height); 82 | banner.frame = newFrame; 83 | banner.alpha = 0.9; 84 | } completion:^(BOOL finished) { 85 | // Empty. 86 | }]; 87 | 88 | 89 | // On timeout, slide it up while fading it out. 90 | if (notification.timeout > 0.0) { 91 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, notification.timeout * NSEC_PER_SEC); 92 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 93 | [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseIn 94 | animations:^{ 95 | banner.frame = CGRectOffset(banner.frame, 0, -banner.frame.size.height); 96 | banner.alpha = 0; 97 | } completion:^(BOOL didFinish) { 98 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 99 | [banner removeFromSuperview]; 100 | [containerView removeFromSuperview]; 101 | finished(); 102 | // Break the retain cycle 103 | notification.tapHandler = nil; 104 | } 105 | }]; 106 | }); 107 | } 108 | } 109 | 110 | #pragma mark - View helpers 111 | 112 | - (JCNotificationBannerWindow*) newWindow { 113 | JCNotificationBannerWindow* window = [super newWindow]; 114 | window.windowLevel = UIWindowLevelStatusBar; 115 | return window; 116 | } 117 | 118 | - (UIView*) newContainerViewForNotification:(JCNotificationBanner*)notification { 119 | UIView* view = [super newContainerViewForNotification:notification]; 120 | view.autoresizesSubviews = YES; 121 | return view; 122 | } 123 | 124 | - (JCNotificationBannerView*) newBannerViewForNotification:(JCNotificationBanner*)notification { 125 | JCNotificationBannerViewIOS7Style* view = [[JCNotificationBannerViewIOS7Style alloc] 126 | initWithNotification:notification]; 127 | view.userInteractionEnabled = YES; 128 | view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin 129 | | UIViewAutoresizingFlexibleLeftMargin 130 | | UIViewAutoresizingFlexibleRightMargin; 131 | return view; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterIOSStyle.h: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerPresenter.h" 2 | 3 | @interface JCNotificationBannerPresenterIOSStyle : JCNotificationBannerPresenter 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterIOSStyle.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerPresenterIOSStyle.h" 2 | #import "JCNotificationBannerPresenter_Private.h" 3 | #import "JCNotificationBannerWindow.h" 4 | #import "JCNotificationBannerViewIOSStyle.h" 5 | #import "JCNotificationBannerView.h" 6 | #import "JCNotificationBannerViewController.h" 7 | 8 | @implementation JCNotificationBannerPresenterIOSStyle 9 | 10 | - (void) presentNotification:(JCNotificationBanner *)notification 11 | inWindow:(JCNotificationBannerWindow*)window 12 | finished:(JCNotificationBannerPresenterFinishedBlock)finished { 13 | JCNotificationBannerView* banner = [self newBannerViewForNotification:notification]; 14 | 15 | JCNotificationBannerViewController* bannerViewController = [JCNotificationBannerViewController new]; 16 | window.rootViewController = bannerViewController; 17 | UIView* originalControllerView = bannerViewController.view; 18 | 19 | UIView* containerView = [self newContainerViewForNotification:notification]; 20 | [containerView addSubview:banner]; 21 | bannerViewController.view = containerView; 22 | 23 | window.bannerView = banner; 24 | 25 | containerView.bounds = originalControllerView.bounds; 26 | containerView.transform = originalControllerView.transform; 27 | [banner getCurrentPresentingStateAndAtomicallySetPresentingState:YES]; 28 | 29 | CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size; 30 | CGFloat width = 320.0; 31 | CGFloat height = 60.0; 32 | CGFloat x = (MAX(statusBarSize.width, statusBarSize.height) - width) / 2.0; 33 | CGFloat y = -60.0; 34 | banner.frame = CGRectMake(x, y, width, height); 35 | 36 | JCNotificationBannerTapHandlingBlock originalTapHandler = notification.tapHandler; 37 | JCNotificationBannerTapHandlingBlock wrappingTapHandler = ^{ 38 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 39 | if (originalTapHandler) { 40 | originalTapHandler(); 41 | } 42 | 43 | [banner removeFromSuperview]; 44 | finished(); 45 | } 46 | // Break the retain cycle 47 | notification.tapHandler = nil; 48 | }; 49 | notification.tapHandler = wrappingTapHandler; 50 | 51 | double startOpacity = 1.0; 52 | double endOpacity = 1.0; 53 | double animationDuration = 0.5; 54 | 55 | CGRect bannerFrameAfterTransition = banner.frame; 56 | bannerFrameAfterTransition.origin.y = MIN(statusBarSize.width, statusBarSize.height); 57 | UIImage* image = [self captureWindowPartWithRect:bannerFrameAfterTransition]; 58 | 59 | // Prepare view transform 60 | CALayer* layer = banner.layer; 61 | banner.alpha = startOpacity; 62 | banner.frame = CGRectOffset(banner.frame, 0.0, banner.frame.size.height); 63 | banner.alpha = endOpacity; 64 | layer.anchorPointZ = 0.5f * banner.frame.size.height; 65 | [self rotateLayer:layer fromAngle:-90.0 toAngle:0.0 duration:animationDuration onCompleted:^(){}]; 66 | 67 | // Add image of background to layer. 68 | CALayer* imageLayer = [CALayer layer]; 69 | imageLayer.frame = banner.frame; 70 | imageLayer.anchorPointZ = 0.5f * banner.frame.size.height; 71 | imageLayer.contents = (id)image.CGImage; 72 | imageLayer.shadowOffset = CGSizeMake(0.0, 1.0); 73 | imageLayer.shadowColor = [UIColor darkGrayColor].CGColor; 74 | imageLayer.shadowRadius = 3.0; 75 | imageLayer.shadowOpacity = 0.8; 76 | [self rotateLayer:imageLayer fromAngle:0.0 toAngle:90.0 duration:animationDuration onCompleted:^(){} ]; 77 | [containerView.layer addSublayer:imageLayer]; 78 | 79 | // On timeout, slide it up while fading it out. 80 | if (notification.timeout > 0.0) { 81 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, notification.timeout * NSEC_PER_SEC); 82 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 83 | // Add image of background to layer. 84 | CALayer* imageLayer = [CALayer layer]; 85 | imageLayer.frame = banner.frame; 86 | imageLayer.anchorPointZ = 0.5f * banner.frame.size.height; 87 | imageLayer.contents = (id)image.CGImage; 88 | imageLayer.shadowOffset = CGSizeMake(0, 1); 89 | imageLayer.shadowColor = [UIColor darkGrayColor].CGColor; 90 | imageLayer.shadowRadius = 3.0; 91 | imageLayer.shadowOpacity = 0.8; 92 | [self rotateLayer:imageLayer fromAngle:-90.0 toAngle:0.0 duration:animationDuration onCompleted:^(){} ]; 93 | [[containerView layer] addSublayer:imageLayer]; 94 | 95 | CALayer* layer = [banner layer]; 96 | [self rotateLayer:layer fromAngle:0.0 toAngle:90.0 duration:animationDuration onCompleted:^(){ 97 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 98 | [banner removeFromSuperview]; 99 | [containerView removeFromSuperview]; 100 | finished(); 101 | } 102 | // Break the retain cycle 103 | notification.tapHandler = nil; 104 | }]; 105 | }); 106 | } 107 | } 108 | 109 | #pragma mark - View helpers 110 | 111 | - (UIView*) newContainerViewForNotification:(JCNotificationBanner*)notification { 112 | UIView* view = [super newContainerViewForNotification:notification]; 113 | view.autoresizesSubviews = YES; 114 | return view; 115 | } 116 | 117 | - (JCNotificationBannerView*) newBannerViewForNotification:(JCNotificationBanner*)notification { 118 | JCNotificationBannerView* view = [[JCNotificationBannerViewIOSStyle alloc] 119 | initWithNotification:notification]; 120 | view.userInteractionEnabled = YES; 121 | view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin 122 | | UIViewAutoresizingFlexibleLeftMargin 123 | | UIViewAutoresizingFlexibleRightMargin; 124 | return view; 125 | } 126 | 127 | #pragma mark - Screenshot 128 | 129 | /** 130 | * @returns part of the keyWindow screenshot rotated by 180 degrees. 131 | */ 132 | - (UIImage*) captureWindowPartWithRect:(CGRect)rect { 133 | UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow]; 134 | 135 | CGRect firstCaptureRect = keyWindow.bounds; 136 | 137 | UIGraphicsBeginImageContextWithOptions(firstCaptureRect.size, YES, 0.0); 138 | CGContextRef context = UIGraphicsGetCurrentContext(); 139 | [keyWindow.layer renderInContext:context]; 140 | UIImage* capturedImage = UIGraphicsGetImageFromCurrentImageContext(); 141 | UIGraphicsEndImageContext(); 142 | 143 | CGRect contentRectToCrop = rect; 144 | CGFloat rotationNeeded = 0; 145 | CGRect originalRect = rect; 146 | switch ([UIApplication sharedApplication].statusBarOrientation) { 147 | case UIInterfaceOrientationLandscapeLeft: 148 | rotationNeeded = 90; 149 | contentRectToCrop.origin.x = originalRect.origin.y; 150 | contentRectToCrop.origin.y = keyWindow.bounds.size.height - originalRect.origin.x - originalRect.size.width; 151 | contentRectToCrop.size.width = originalRect.size.height; 152 | contentRectToCrop.size.height = originalRect.size.width; 153 | break; 154 | 155 | case UIInterfaceOrientationLandscapeRight: 156 | rotationNeeded = -90; 157 | contentRectToCrop.origin.x = keyWindow.bounds.size.width - originalRect.origin.y - originalRect.size.height; 158 | contentRectToCrop.origin.y = keyWindow.bounds.size.height - originalRect.origin.x - originalRect.size.width ; 159 | contentRectToCrop.size.width = originalRect.size.height; 160 | contentRectToCrop.size.height = originalRect.size.width; 161 | break; 162 | 163 | case UIInterfaceOrientationPortrait: 164 | case UIInterfaceOrientationUnknown: 165 | break; 166 | 167 | case UIInterfaceOrientationPortraitUpsideDown: 168 | rotationNeeded = 180; 169 | contentRectToCrop.origin.x = originalRect.origin.x; 170 | contentRectToCrop.origin.y = keyWindow.bounds.size.height - originalRect.origin.y - originalRect.size.height; 171 | contentRectToCrop.size.width = originalRect.size.width; 172 | contentRectToCrop.size.height = originalRect.size.height; 173 | break; 174 | } 175 | 176 | contentRectToCrop.origin.x *= capturedImage.scale; 177 | contentRectToCrop.origin.y *= capturedImage.scale; 178 | contentRectToCrop.size.width *= capturedImage.scale; 179 | contentRectToCrop.size.height *= capturedImage.scale; 180 | 181 | CGImageRef imageRef = CGImageCreateWithImageInRect([capturedImage CGImage], contentRectToCrop); 182 | UIImage* croppedImage = [UIImage imageWithCGImage:imageRef scale:capturedImage.scale orientation: UIImageOrientationUp]; 183 | CGImageRelease(imageRef); 184 | 185 | if (rotationNeeded) { 186 | croppedImage = [self.class rotateImage:croppedImage byDegrees:rotationNeeded]; 187 | } 188 | 189 | return croppedImage; 190 | } 191 | 192 | // ----------------------------------------------------------------------- 193 | // UIImage Extensions for preparing screenshots under banner by HardyMacia 194 | // (Catamount Software). 195 | // http://www.catamount.com/forums/viewtopic.php?f=21&t=967 196 | 197 | CGFloat DegreesToRadians(CGFloat degrees) { return degrees * M_PI / 180.0; }; 198 | CGFloat RadiansToDegrees(CGFloat radians) { return radians * 180.0 / M_PI; }; 199 | 200 | + (UIImage*) rotateImage:(UIImage*)image byDegrees:(CGFloat)degrees { 201 | // Calculate the size of the rotated view's containing box for our drawing space 202 | UIView* rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, image.size.width, image.size.height)]; 203 | CGAffineTransform transform = CGAffineTransformMakeRotation(DegreesToRadians(degrees)); 204 | rotatedViewBox.transform = transform; 205 | CGSize rotatedSize = rotatedViewBox.frame.size; 206 | 207 | // Create the bitmap context 208 | UIGraphicsBeginImageContext(rotatedSize); 209 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 210 | 211 | // Move the origin to the middle of the image so we will rotate and scale around the center. 212 | CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2); 213 | 214 | // Rotate the image context 215 | CGContextRotateCTM(bitmap, DegreesToRadians(degrees)); 216 | 217 | // Now, draw the rotated/scaled image into the context 218 | CGContextScaleCTM(bitmap, 1.0, -1.0); 219 | CGRect imageRect = CGRectMake(-image.size.width / 2.0, -image.size.height / 2.0, image.size.width, image.size.height); 220 | CGContextDrawImage(bitmap, imageRect, image.CGImage); 221 | 222 | UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); 223 | UIGraphicsEndImageContext(); 224 | 225 | return newImage; 226 | } 227 | 228 | // ----------------------------------------------------------------------- 229 | 230 | - (void) rotateLayer:(CALayer*)imageLayer 231 | fromAngle:(CGFloat)fromAngle 232 | toAngle:(CGFloat)toAngle 233 | duration:(CFTimeInterval)duration 234 | onCompleted:(void (^)())onCompletedBlock { 235 | CGFloat fromInRadians = fromAngle * M_PI / 180.0f; 236 | CGFloat toInRadians = toAngle * M_PI / 180.0f; 237 | 238 | // Create animation that rotates by to the end rotation. 239 | CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; 240 | animation.delegate = self; 241 | animation.duration = duration; 242 | animation.fromValue = @(fromInRadians); 243 | animation.toValue = @(toInRadians); 244 | animation.fillMode = kCAFillModeForwards; 245 | animation.removedOnCompletion = NO; 246 | [animation setValue:[onCompletedBlock copy] forKey:@"onCompleted"]; 247 | [imageLayer addAnimation:animation forKey:@"transform.rotation.x"]; 248 | } 249 | 250 | - (void) animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { 251 | if (finished) { 252 | void(^onCompletedBlock)() = [animation valueForKey:@"onCompleted"]; 253 | if (onCompletedBlock) 254 | onCompletedBlock(); 255 | } 256 | [animation setValue:nil forKey:@"onCompleted"]; 257 | } 258 | 259 | @end 260 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterSmokeStyle.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBannerPresenter.h" 3 | 4 | @interface JCNotificationBannerPresenterSmokeStyle : JCNotificationBannerPresenter 5 | @property CGFloat minimumHorizontalMargin; 6 | @property CGFloat bannerMaxWidth; 7 | @property CGFloat bannerHeight; 8 | @end 9 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenterSmokeStyle.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerPresenterSmokeStyle.h" 2 | #import "JCNotificationBannerPresenter_Private.h" 3 | #import "JCNotificationBannerView.h" 4 | #import "JCNotificationBannerViewController.h" 5 | 6 | @implementation JCNotificationBannerPresenterSmokeStyle 7 | 8 | - (id) init { 9 | if (self = [super init]) { 10 | self.minimumHorizontalMargin = 10.0; 11 | self.bannerMaxWidth = 350.0; 12 | self.bannerHeight = 60.0; 13 | } 14 | return self; 15 | } 16 | 17 | - (void) presentNotification:(JCNotificationBanner*)notification 18 | inWindow:(JCNotificationBannerWindow*)window 19 | finished:(JCNotificationBannerPresenterFinishedBlock)finished { 20 | JCNotificationBannerView* banner = [self newBannerViewForNotification:notification]; 21 | 22 | JCNotificationBannerViewController* bannerViewController = [JCNotificationBannerViewController new]; 23 | window.rootViewController = bannerViewController; 24 | UIView* originalControllerView = bannerViewController.view; 25 | 26 | UIView* containerView = [self newContainerViewForNotification:notification]; 27 | [containerView addSubview:banner]; 28 | bannerViewController.view = containerView; 29 | 30 | window.bannerView = banner; 31 | 32 | containerView.bounds = originalControllerView.bounds; 33 | containerView.transform = originalControllerView.transform; 34 | [banner getCurrentPresentingStateAndAtomicallySetPresentingState:YES]; 35 | 36 | CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size; 37 | // Make the banner fill the width of the screen, minus any requested margins, 38 | // up to self.bannerMaxWidth. 39 | CGSize bannerSize = CGSizeMake(MIN(self.bannerMaxWidth, originalControllerView.bounds.size.width - self.minimumHorizontalMargin * 2.0), self.bannerHeight); 40 | // Center the banner horizontally. 41 | CGFloat x = (MAX(statusBarSize.width, statusBarSize.height) / 2) - (bannerSize.width / 2); 42 | // Position the banner offscreen vertically. 43 | CGFloat y = -self.bannerHeight; 44 | 45 | #ifdef __IPHONE_7_0 46 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 47 | BOOL accountForStatusBarHeight = ([[UIDevice currentDevice].systemVersion intValue] < 7); 48 | #else 49 | // We're building for a pre-iOS7 base SDK. 50 | BOOL accountForStatusBarHeight = YES; 51 | #endif 52 | #else 53 | // We don't even have access to the iOS 7 availability constants. 54 | BOOL accountForStatusBarHeight = YES; 55 | #endif 56 | if (accountForStatusBarHeight) { 57 | y -= (MIN(statusBarSize.width, statusBarSize.height)); 58 | } 59 | 60 | banner.frame = CGRectMake(x, y, bannerSize.width, bannerSize.height); 61 | 62 | JCNotificationBannerTapHandlingBlock originalTapHandler = banner.notificationBanner.tapHandler; 63 | JCNotificationBannerTapHandlingBlock wrappingTapHandler = ^{ 64 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 65 | if (originalTapHandler) { 66 | originalTapHandler(); 67 | } 68 | 69 | [banner removeFromSuperview]; 70 | finished(); 71 | // Break the retain cycle 72 | notification.tapHandler = nil; 73 | } 74 | }; 75 | banner.notificationBanner.tapHandler = wrappingTapHandler; 76 | 77 | // Slide it down while fading it in. 78 | banner.alpha = 0; 79 | [UIView animateWithDuration:0.5 delay:0 80 | options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut 81 | animations:^{ 82 | CGRect newFrame = CGRectOffset(banner.frame, 0, banner.frame.size.height); 83 | banner.frame = newFrame; 84 | banner.alpha = 0.9; 85 | } completion:^(BOOL finished) { 86 | // Empty. 87 | }]; 88 | 89 | 90 | // On timeout, slide it up while fading it out. 91 | if (notification.timeout > 0.0) { 92 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, notification.timeout * NSEC_PER_SEC); 93 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 94 | [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseIn 95 | animations:^{ 96 | banner.frame = CGRectOffset(banner.frame, 0, -banner.frame.size.height); 97 | banner.alpha = 0; 98 | } completion:^(BOOL didFinish) { 99 | if ([banner getCurrentPresentingStateAndAtomicallySetPresentingState:NO]) { 100 | [banner removeFromSuperview]; 101 | [containerView removeFromSuperview]; 102 | finished(); 103 | // Break the retain cycle 104 | notification.tapHandler = nil; 105 | } 106 | }]; 107 | }); 108 | } 109 | } 110 | 111 | #pragma mark - View helpers 112 | 113 | - (JCNotificationBannerWindow*) newWindow { 114 | JCNotificationBannerWindow* window = [super newWindow]; 115 | window.windowLevel = UIWindowLevelStatusBar; 116 | return window; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerPresenter_Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBannerWindow.h" 3 | #import "JCNotificationBannerView.h" 4 | 5 | @interface JCNotificationBannerPresenter () { 6 | @private 7 | JCNotificationBannerWindow* bannerWindow; 8 | } 9 | 10 | - (void) presentNotification:(JCNotificationBanner*)notification 11 | inWindow:(JCNotificationBannerWindow*)window 12 | finished:(JCNotificationBannerPresenterFinishedBlock)finished; 13 | 14 | #pragma mark - View helpers 15 | - (UIView*) newContainerViewForNotification:(JCNotificationBanner*)notification; 16 | - (JCNotificationBannerWindow*) newWindow; 17 | - (JCNotificationBannerView*) newBannerViewForNotification:(JCNotificationBanner*)notification; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "JCNotificationBanner.h" 4 | 5 | @interface JCNotificationBannerView : UIView 6 | 7 | @property (nonatomic) JCNotificationBanner* notificationBanner; 8 | @property (nonatomic) UIImageView* iconImageView; 9 | @property (nonatomic) UILabel* titleLabel; 10 | @property (nonatomic) UILabel* messageLabel; 11 | 12 | - (id) initWithNotification:(JCNotificationBanner*)notification; 13 | - (BOOL) getCurrentPresentingStateAndAtomicallySetPresentingState:(BOOL)state; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerView.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerView.h" 2 | 3 | const CGFloat kJCNotificationBannerViewOutlineWidth = 2.0; 4 | const CGFloat kJCNotificationBannerViewMarginX = 15.0; 5 | const CGFloat kJCNotificationBannerViewMarginY = 5.0; 6 | 7 | @interface JCNotificationBannerView () { 8 | BOOL isPresented; 9 | NSObject* isPresentedMutex; 10 | } 11 | 12 | - (void) handleSingleTap:(UIGestureRecognizer*)gestureRecognizer; 13 | 14 | @end 15 | 16 | @implementation JCNotificationBannerView 17 | 18 | @synthesize notificationBanner; 19 | @synthesize iconImageView; 20 | @synthesize titleLabel; 21 | @synthesize messageLabel; 22 | 23 | - (id) initWithNotification:(JCNotificationBanner*)notification { 24 | self = [super init]; 25 | if (self) { 26 | isPresentedMutex = [NSObject new]; 27 | 28 | self.backgroundColor = [UIColor clearColor]; 29 | self.iconImageView = [UIImageView new]; 30 | [self addSubview:self.iconImageView]; 31 | self.titleLabel = [UILabel new]; 32 | self.titleLabel.font = [UIFont boldSystemFontOfSize:16]; 33 | self.titleLabel.textColor = [UIColor lightTextColor]; 34 | self.titleLabel.backgroundColor = [UIColor clearColor]; 35 | [self addSubview:self.titleLabel]; 36 | self.messageLabel = [UILabel new]; 37 | self.messageLabel.font = [UIFont systemFontOfSize:14]; 38 | self.messageLabel.textColor = [UIColor lightTextColor]; 39 | self.messageLabel.backgroundColor = [UIColor clearColor]; 40 | self.messageLabel.numberOfLines = 0; 41 | [self addSubview:self.messageLabel]; 42 | 43 | UITapGestureRecognizer* tapRecognizer; 44 | tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 45 | action:@selector(handleSingleTap:)]; 46 | [self addGestureRecognizer:tapRecognizer]; 47 | 48 | self.notificationBanner = notification; 49 | } 50 | return self; 51 | } 52 | 53 | - (void) drawRect:(CGRect)rect { 54 | CGRect bounds = self.bounds; 55 | 56 | CGFloat lineWidth = kJCNotificationBannerViewOutlineWidth; 57 | CGFloat radius = 10; 58 | CGFloat height = bounds.size.height; 59 | CGFloat width = bounds.size.width; 60 | 61 | CGContextRef context = UIGraphicsGetCurrentContext(); 62 | 63 | CGContextSetAllowsAntialiasing(context, true); 64 | CGContextSetShouldAntialias(context, true); 65 | 66 | CGMutablePathRef outlinePath = CGPathCreateMutable(); 67 | 68 | CGPathMoveToPoint(outlinePath, NULL, lineWidth, 0); 69 | CGPathAddLineToPoint(outlinePath, NULL, lineWidth, height - radius - lineWidth); 70 | CGPathAddArc(outlinePath, NULL, radius + lineWidth, height - radius - lineWidth, radius, -M_PI, M_PI_2, 1); 71 | CGPathAddLineToPoint(outlinePath, NULL, width - radius - lineWidth, height - lineWidth); 72 | CGPathAddArc(outlinePath, NULL, width - radius - lineWidth, height - radius - lineWidth, radius, M_PI_2, 0, 1); 73 | CGPathAddLineToPoint(outlinePath, NULL, width - lineWidth, 0); 74 | 75 | CGContextSetRGBFillColor(context, 0, 0, 0, 0.9); 76 | CGContextAddPath(context, outlinePath); 77 | CGContextFillPath(context); 78 | 79 | CGContextAddPath(context, outlinePath); 80 | CGContextSetRGBFillColor(context, 0, 0, 0, 1); 81 | CGContextSetLineWidth(context, lineWidth); 82 | CGContextDrawPath(context, kCGPathStroke); 83 | 84 | CGPathRelease(outlinePath); 85 | } 86 | 87 | - (void) layoutSubviews { 88 | if (!(self.frame.size.width > 0)) { return; } 89 | 90 | BOOL hasTitle = notificationBanner ? (notificationBanner.title.length > 0) : NO; 91 | 92 | CGFloat borderY = kJCNotificationBannerViewOutlineWidth + kJCNotificationBannerViewMarginY; 93 | CGFloat borderX = kJCNotificationBannerViewOutlineWidth + kJCNotificationBannerViewMarginX; 94 | CGFloat currentX = borderX; 95 | CGFloat currentY = borderY; 96 | CGFloat contentWidth = self.frame.size.width - (borderX * 2.0); 97 | 98 | currentY += 2.0; 99 | if (hasTitle) { 100 | self.titleLabel.frame = CGRectMake(currentX, currentY, contentWidth, 22.0); 101 | currentY += 22.0; 102 | } 103 | self.messageLabel.frame = CGRectMake(currentX, currentY, contentWidth, (self.frame.size.height - borderY) - currentY); 104 | [self.messageLabel sizeToFit]; 105 | CGRect messageFrame = self.messageLabel.frame; 106 | CGFloat spillY = (currentY + messageFrame.size.height + kJCNotificationBannerViewMarginY) - self.frame.size.height; 107 | if (spillY > 0.0) { 108 | messageFrame.size.height -= spillY; 109 | self.messageLabel.frame = messageFrame; 110 | } 111 | } 112 | 113 | - (void) setNotificationBanner:(JCNotificationBanner*)notification { 114 | notificationBanner = notification; 115 | 116 | self.titleLabel.text = notification.title; 117 | self.messageLabel.text = notification.message; 118 | } 119 | 120 | - (void) handleSingleTap:(UIGestureRecognizer *)gestureRecognizer { 121 | if (notificationBanner && notificationBanner.tapHandler) { 122 | notificationBanner.tapHandler(); 123 | } 124 | } 125 | 126 | - (BOOL) getCurrentPresentingStateAndAtomicallySetPresentingState:(BOOL)state { 127 | @synchronized(isPresentedMutex) { 128 | BOOL originalState = isPresented; 129 | isPresented = state; 130 | return originalState; 131 | } 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // JCNotificationBannerViewController.h 3 | // GlobalDriver2ClientBooking 4 | // 5 | // Created by James Coleman on 8/26/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface JCNotificationBannerViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // JCNotificationBannerViewController.m 3 | // GlobalDriver2ClientBooking 4 | // 5 | // Created by James Coleman on 8/26/12. 6 | // 7 | // 8 | 9 | #import "JCNotificationBannerViewController.h" 10 | 11 | @interface JCNotificationBannerViewController () 12 | 13 | @end 14 | 15 | @implementation JCNotificationBannerViewController 16 | 17 | - (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 18 | // TODO: return result of main window's controller 19 | return YES; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewIOS7Style.h: -------------------------------------------------------------------------------- 1 | // 2 | // JCNotificationBannerViewIOS7Style.h 3 | // JCNotificationBannerPresenterDemo 4 | // 5 | // Created by James Coleman on 1/5/14. 6 | // Copyright (c) 2014 James Coleman. All rights reserved. 7 | // 8 | 9 | #import "JCNotificationBannerView.h" 10 | 11 | @interface JCNotificationBannerViewIOS7Style : JCNotificationBannerView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewIOS7Style.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerViewIOS7Style.h" 2 | 3 | @implementation JCNotificationBannerViewIOS7Style 4 | 5 | - (id) initWithNotification:(JCNotificationBanner*)notification { 6 | self = [super initWithNotification:notification]; 7 | if (self) { 8 | self.titleLabel.textColor = [UIColor whiteColor]; 9 | self.messageLabel.textColor = [UIColor whiteColor]; 10 | self.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.9]; 11 | 12 | /*self.layer.shadowOffset = CGSizeMake(0, 1); 13 | self.layer.shadowColor = [UIColor darkGrayColor].CGColor; 14 | self.layer.shadowRadius = 3.0; 15 | self.layer.shadowOpacity = 0.8;*/ 16 | } 17 | return self; 18 | } 19 | 20 | /** Overriden to do no custom drawing */ 21 | - (void) drawRect:(CGRect)rect { 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewIOSStyle.h: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerView.h" 2 | 3 | @interface JCNotificationBannerViewIOSStyle : JCNotificationBannerView 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerViewIOSStyle.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerViewIOSStyle.h" 2 | 3 | @implementation JCNotificationBannerViewIOSStyle 4 | 5 | - (id) initWithNotification:(JCNotificationBanner*)notification { 6 | self = [super initWithNotification:notification]; 7 | if (self) { 8 | self.titleLabel.textColor = [UIColor darkTextColor]; 9 | self.messageLabel.textColor = [UIColor darkTextColor]; 10 | self.backgroundColor = [UIColor whiteColor]; 11 | 12 | self.layer.shadowOffset = CGSizeMake(0, 1); 13 | self.layer.shadowColor = [UIColor darkGrayColor].CGColor; 14 | self.layer.shadowRadius = 3.0; 15 | self.layer.shadowOpacity = 0.8; 16 | } 17 | return self; 18 | } 19 | 20 | /** Overriden to do no custom drawing */ 21 | - (void) drawRect:(CGRect)rect { 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerWindow.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface JCNotificationBannerWindow : UIWindow 4 | 5 | @property (nonatomic) UIView* bannerView; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Library/JCNotificationBannerWindow.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationBannerWindow.h" 2 | 3 | @implementation JCNotificationBannerWindow 4 | 5 | @synthesize bannerView; 6 | 7 | - (id) initWithFrame:(CGRect)frame { 8 | self = [super initWithFrame:frame]; 9 | if (self) { 10 | // Initialization code 11 | } 12 | return self; 13 | } 14 | 15 | - (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event { 16 | UIView* superHitView = [super hitTest:point withEvent:event]; 17 | if (superHitView == bannerView) { 18 | return bannerView; 19 | } else { 20 | UIWindow* nextWindow; 21 | BOOL encounteredBannerWindow = NO; 22 | for (UIWindow* window in [[UIApplication sharedApplication].windows reverseObjectEnumerator]) { 23 | if (encounteredBannerWindow && ![window isKindOfClass:[JCNotificationBannerWindow class]]) { 24 | nextWindow = window; 25 | break; 26 | } 27 | 28 | if (!encounteredBannerWindow && [window isKindOfClass:[JCNotificationBannerWindow class]]) { 29 | encounteredBannerWindow = YES; 30 | } 31 | } 32 | 33 | if (nextWindow) { 34 | return [nextWindow hitTest:point withEvent:event]; 35 | } else { 36 | return superHitView; 37 | } 38 | } 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Library/JCNotificationCenter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "JCNotificationBanner.h" 3 | 4 | @class JCNotificationBannerPresenter; 5 | 6 | @interface JCNotificationCenter : NSObject 7 | 8 | @property (nonatomic) JCNotificationBannerPresenter* presenter; 9 | 10 | + (JCNotificationCenter*) sharedCenter; 11 | 12 | /** Adds notification to queue with given parameters. */ 13 | + (void) enqueueNotificationWithTitle:(NSString*)title 14 | message:(NSString*)message 15 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler; 16 | 17 | - (void) enqueueNotificationWithTitle:(NSString*)title 18 | message:(NSString*)message 19 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler; 20 | 21 | - (void) enqueueNotification:(JCNotificationBanner*)notification; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Library/JCNotificationCenter.m: -------------------------------------------------------------------------------- 1 | #import "JCNotificationCenter.h" 2 | #import "JCNotificationBannerPresenter.h" 3 | #import "JCNotificationBannerPresenterIOSStyle.h" 4 | #import "JCNotificationBannerWindow.h" 5 | #import "JCNotificationBannerPresenter_Private.h" 6 | 7 | @interface JCNotificationCenter () { 8 | @private 9 | NSMutableArray* enqueuedNotifications; 10 | NSLock* isPresentingMutex; 11 | NSObject* notificationQueueMutex; 12 | JCNotificationBannerPresenter* _currentPresenter; 13 | JCNotificationBannerPresenter* _nextPresenter; 14 | } 15 | @end 16 | 17 | @implementation JCNotificationCenter 18 | 19 | - (JCNotificationCenter*) init { 20 | self = [super init]; 21 | if (self) { 22 | enqueuedNotifications = [NSMutableArray new]; 23 | isPresentingMutex = [NSLock new]; 24 | notificationQueueMutex = [NSObject new]; 25 | self.presenter = [[[self class] presenterClass] new]; 26 | } 27 | return self; 28 | } 29 | 30 | + (JCNotificationCenter*) sharedCenter { 31 | static JCNotificationCenter* sharedCenter = nil; 32 | static dispatch_once_t onceToken; 33 | dispatch_once(&onceToken, ^{ 34 | sharedCenter = [[self class] new]; 35 | }); 36 | return sharedCenter; 37 | } 38 | 39 | + (Class) presenterClass { 40 | return [JCNotificationBannerPresenterIOSStyle class]; 41 | } 42 | 43 | /** Adds notification with iOS banner Style to queue with given parameters. */ 44 | + (void) enqueueNotificationWithTitle:(NSString*)title 45 | message:(NSString*)message 46 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler { 47 | JCNotificationBanner* notification = [[JCNotificationBanner alloc] initWithTitle:title 48 | message:message 49 | tapHandler:tapHandler]; 50 | 51 | [[self sharedCenter] enqueueNotification:notification]; 52 | } 53 | 54 | - (void) enqueueNotificationWithTitle:(NSString*)title 55 | message:(NSString*)message 56 | tapHandler:(JCNotificationBannerTapHandlingBlock)tapHandler { 57 | JCNotificationBanner* notification = [[JCNotificationBanner alloc] 58 | initWithTitle:title 59 | message:message 60 | tapHandler:tapHandler]; 61 | [self enqueueNotification:notification]; 62 | } 63 | 64 | - (void) enqueueNotification:(JCNotificationBanner*)notification { 65 | @synchronized(notificationQueueMutex) { 66 | [enqueuedNotifications addObject:notification]; 67 | } 68 | [self beginPresentingNotifications]; 69 | } 70 | 71 | - (JCNotificationBanner*) dequeueNotification { 72 | JCNotificationBanner* notification; 73 | @synchronized(notificationQueueMutex) { 74 | if ([enqueuedNotifications count] > 0) { 75 | notification = [enqueuedNotifications objectAtIndex:0]; 76 | [enqueuedNotifications removeObjectAtIndex:0]; 77 | } 78 | } 79 | return notification; 80 | } 81 | 82 | - (void) beginPresentingNotifications { 83 | dispatch_async(dispatch_get_main_queue(), ^{ 84 | if ([isPresentingMutex tryLock]) { 85 | // Check to see if the nextPresenter has changed *during* a group 86 | // of notifications. 87 | if (_currentPresenter != _nextPresenter) { 88 | // Finish up with the original one. 89 | [_currentPresenter didFinishPresentingNotifications]; 90 | _currentPresenter = nil; 91 | } 92 | 93 | if (!_currentPresenter) { 94 | _currentPresenter = _nextPresenter; 95 | [_currentPresenter willBeginPresentingNotifications]; 96 | } 97 | JCNotificationBanner* nextNotification = [self dequeueNotification]; 98 | if (nextNotification) { 99 | [_currentPresenter presentNotification:nextNotification 100 | finished:^{ 101 | [self donePresentingNotification:nextNotification]; 102 | }]; 103 | } else { 104 | [_currentPresenter didFinishPresentingNotifications]; 105 | _currentPresenter = nil; 106 | [isPresentingMutex unlock]; 107 | } 108 | } else { 109 | // Notification presentation already in progress; do nothing. 110 | } 111 | }); 112 | } 113 | 114 | - (void) donePresentingNotification:(JCNotificationBanner*)notification { 115 | // Process any notifications enqueued during this one's presentation. 116 | [isPresentingMutex unlock]; 117 | [self beginPresentingNotifications]; 118 | } 119 | 120 | - (void) setPresenter:(JCNotificationBannerPresenter*)presenter { 121 | _nextPresenter = presenter; 122 | } 123 | 124 | - (JCNotificationBannerPresenter*) presenter { 125 | return _nextPresenter; 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Why? 2 | ---- 3 | 4 | Apple doesn't provide an API in iOS for presenting notification banners while the app is running. For many projects the most sensible way to respond to a remote push notification would be to display that notification (in the same style it is presented if your app is not foreground) and allow the user to determine whether the notification merits a response. 5 | 6 | What? 7 | ----- 8 | 9 | JCNotificationBannerPresenter allows you to display notification banners asynchronously from any piece of code in your application. It is not view/view-controller specific, you can use it directly from the AppDelegate, for example. It doesn't attempt to mimic the style of Apple's notifications; it seems better to avoid the uncanny valley presented by that endeavor. Just like Apple's notifications, however, this project slides down a banner from the middle of the status bar that times out after a set period of time and slides back up. The banner also can be dismissed by tapping on it, and a custom tap handler can be assigned to trigger some custom action. 10 | 11 | Usage? 12 | ---- 13 | 14 | #import "JCNotificationCenter.h" 15 | 16 | … 17 | 18 | - (void) application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)notification { 19 | NSString* title = @"Push Notification"; 20 | NSDictionary* aps = [notification objectForKey:@"aps"]; 21 | NSString* alert = [aps objectForKey:@"alert"]; 22 | [JCNotificationCenter 23 | enqueueNotificationWithTitle:title 24 | message:alert 25 | tapHandler:^{ 26 | NSLog(@"Received tap on notification banner!"); 27 | }]; 28 | } 29 | 30 | By default the banner style is iOS style, which presents banners with a 3D rotation. The project comes with an alternative Smoke appearance, which slides a semi-translucent window down from the status bar. If you want to use the same style for all notifications in your project, you can set the style at compile time by overriding or editing the `+presenterClass` method in JCNotificationCenter. 31 | 32 | Notifications enqueued with the method `+enqueueNotificationWithTitle:message:tapHandler:` will time out in five seconds. You can change this on a per-notification basis by setting the timeout property in JCNotificationBanner and enqueing the banner with `-enqueueNotification:`. A value <= 0 will never timeout and requires user interaction to dismiss. 33 | 34 | Installation? 35 | ------------- 36 | 37 | This project includes a `podspec` for usage with [CocoaPods](http://http://cocoapods.org/). Simply add 38 | 39 | pod 'JCNotificationBannerPresenter' 40 | 41 | to your `Podfile` and run `pod install`. 42 | 43 | Alternately, you can configure your project to include the `QuartzCore` (`CoreGraphics` already includes `QuartzCore` ) framework and add all of the `.h` and `.m` files in this project. If your project does not use ARC, you will need to enable ARC on these files. You can enabled ARC per-file by adding the -fobjc-arc flag, as described on [a common StackOverflow question](http://stackoverflow.com/questions/6646052/how-can-i-disable-arc-for-a-single-file-in-a-project). 44 | 45 | License 46 | ------- 47 | 48 | This project is licensed under the MIT license. All copyright rights are retained by myself. 49 | 50 | Copyright (c) 2012 James Coleman 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | --------------------------------------------------------------------------------