├── .gitignore ├── JXMarqueeView.podspec ├── JXMarqueeView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── JXMarqueeView ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Assets │ ├── haizeiwang.jpeg │ ├── left.gif │ ├── picture.gif │ ├── poetry.gif │ ├── reverse.gif │ └── right.gif ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CustomCopyView.swift ├── CustomCopyViewController.swift ├── CustomView.swift ├── CustomViewController.swift ├── Info.plist ├── MarqueeImageViewViewController.swift ├── MarqueeViewController.swift ├── UILabel+VerticalText.swift └── ViewController.swift ├── LICENSE ├── README.md └── Sources └── JXMarqueeView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /JXMarqueeView.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint JXMarqueeView.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "JXMarqueeView" 19 | s.version = "0.1.1" 20 | s.summary = "A powerful and easy to use marquee view." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | # s.description = "A powerful and easy to use marquee view. Happy to use!" 28 | 29 | s.homepage = "https://github.com/pujiaxin33/JXMarqueeView" 30 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 31 | 32 | 33 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 34 | # 35 | # Licensing your code is important. See http://choosealicense.com for more info. 36 | # CocoaPods will detect a license file if there is a named LICENSE* 37 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 38 | # 39 | 40 | s.license = 'MIT' 41 | # s.license = { :type => "MIT", :file => "LICENSE" } 42 | 43 | 44 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 45 | # 46 | # Specify the authors of the library, with email addresses. Email addresses 47 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 48 | # accepts just a name if you'd rather not provide an email address. 49 | # 50 | # Specify a social_media_url where others can refer to, for example a twitter 51 | # profile URL. 52 | # 53 | 54 | s.author = { "pujiaxin33" => "317437084@qq.com" } 55 | # Or just: s.author = "pjx" 56 | # s.authors = { "pjx" => "pujx@jingbo360.com" } 57 | # s.social_media_url = "http://twitter.com/pjx" 58 | 59 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 60 | # 61 | # If this Pod runs only on iOS or OS X, then specify the platform and 62 | # the deployment target. You can optionally include the target after the platform. 63 | # 64 | 65 | # s.platform = :ios 66 | s.platform = :ios, "10.0" 67 | 68 | s.swift_version = "5.0" 69 | 70 | # When using multiple platforms 71 | # s.ios.deployment_target = "5.0" 72 | # s.osx.deployment_target = "10.7" 73 | # s.watchos.deployment_target = "2.0" 74 | # s.tvos.deployment_target = "10.0" 75 | 76 | 77 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 78 | # 79 | # Specify the location from where the source should be retrieved. 80 | # Supports git, hg, bzr, svn and HTTP. 81 | # 82 | 83 | s.source = { :git => "https://github.com/pujiaxin33/JXMarqueeView.git", :tag => "#{s.version}" } 84 | 85 | 86 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 87 | # 88 | # CocoaPods is smart about how it includes source code. For source files 89 | # giving a folder will include any swift, h, m, mm, c & cpp files. 90 | # For header files it will include any header in the folder. 91 | # Not including the public_header_files will make all headers public. 92 | # 93 | 94 | s.source_files = "Sources/**/*.{swift}" 95 | # s.exclude_files = "Classes/Exclude" 96 | 97 | # s.public_header_files = "Sources/**/*.{swift}" 98 | 99 | 100 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 101 | # 102 | # A list of resources included with the Pod. These are copied into the 103 | # target bundle with a build phase script. Anything else will be cleaned. 104 | # You can preserve files from being cleaned, please don't preserve 105 | # non-essential files like tests, examples and documentation. 106 | # 107 | 108 | # s.resource = "icon.png" 109 | # s.resources = "Resources/*.png" 110 | 111 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 112 | 113 | 114 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 115 | # 116 | # Link your library with frameworks, or libraries. Libraries do not include 117 | # the lib prefix of their name. 118 | # 119 | 120 | s.framework = "UIKit" 121 | # s.frameworks = "SomeFramework", "AnotherFramework" 122 | 123 | # s.library = "iconv" 124 | # s.libraries = "iconv", "xml2" 125 | 126 | 127 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 128 | # 129 | # If your library depends on compiler flags you can set them in the xcconfig hash 130 | # where they will only apply to your library. If you depend on other Podspecs 131 | # you can include multiple dependencies to ensure it works. 132 | 133 | s.requires_arc = true 134 | 135 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 136 | # s.dependency "JSONKit", "~> 1.4" 137 | 138 | end 139 | -------------------------------------------------------------------------------- /JXMarqueeView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 192AD15E212FD16C0031AD46 /* JXMarqueeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192AD15D212FD16C0031AD46 /* JXMarqueeView.swift */; }; 11 | 192AD169212FFD660031AD46 /* CustomCopyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192AD168212FFD660031AD46 /* CustomCopyViewController.swift */; }; 12 | 192AD16B212FFD780031AD46 /* CustomCopyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192AD16A212FFD780031AD46 /* CustomCopyView.swift */; }; 13 | 19F166E820968DC000C2C03F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F166E720968DC000C2C03F /* AppDelegate.swift */; }; 14 | 19F166EA20968DC000C2C03F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F166E920968DC000C2C03F /* ViewController.swift */; }; 15 | 19F166ED20968DC000C2C03F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19F166EB20968DC000C2C03F /* Main.storyboard */; }; 16 | 19F166EF20968DC100C2C03F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19F166EE20968DC100C2C03F /* Assets.xcassets */; }; 17 | 19F166F220968DC100C2C03F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19F166F020968DC100C2C03F /* LaunchScreen.storyboard */; }; 18 | 19F166FD2098632D00C2C03F /* MarqueeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F166FC2098632D00C2C03F /* MarqueeViewController.swift */; }; 19 | 19F167012098689800C2C03F /* MarqueeImageViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F167002098689800C2C03F /* MarqueeImageViewViewController.swift */; }; 20 | 19F1670320994B6800C2C03F /* haizeiwang.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670220994B6800C2C03F /* haizeiwang.jpeg */; }; 21 | 19F1670520994FD500C2C03F /* CustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F1670420994FD500C2C03F /* CustomView.swift */; }; 22 | 19F167072099505D00C2C03F /* UILabel+VerticalText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F167062099505D00C2C03F /* UILabel+VerticalText.swift */; }; 23 | 19F167092099535200C2C03F /* CustomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F167082099535200C2C03F /* CustomViewController.swift */; }; 24 | 19F167102099692900C2C03F /* left.gif in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670B2099692900C2C03F /* left.gif */; }; 25 | 19F167112099692900C2C03F /* poetry.gif in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670C2099692900C2C03F /* poetry.gif */; }; 26 | 19F167122099692900C2C03F /* reverse.gif in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670D2099692900C2C03F /* reverse.gif */; }; 27 | 19F167132099692900C2C03F /* right.gif in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670E2099692900C2C03F /* right.gif */; }; 28 | 19F167142099692900C2C03F /* picture.gif in Resources */ = {isa = PBXBuildFile; fileRef = 19F1670F2099692900C2C03F /* picture.gif */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 192AD15D212FD16C0031AD46 /* JXMarqueeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JXMarqueeView.swift; sourceTree = ""; }; 33 | 192AD168212FFD660031AD46 /* CustomCopyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCopyViewController.swift; sourceTree = ""; }; 34 | 192AD16A212FFD780031AD46 /* CustomCopyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCopyView.swift; sourceTree = ""; }; 35 | 19F166E420968DC000C2C03F /* JXMarqueeView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JXMarqueeView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 19F166E720968DC000C2C03F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 19F166E920968DC000C2C03F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 38 | 19F166EC20968DC000C2C03F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 19F166EE20968DC100C2C03F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 19F166F120968DC100C2C03F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 19F166F320968DC100C2C03F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 19F166FC2098632D00C2C03F /* MarqueeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarqueeViewController.swift; sourceTree = ""; }; 43 | 19F167002098689800C2C03F /* MarqueeImageViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarqueeImageViewViewController.swift; sourceTree = ""; }; 44 | 19F1670220994B6800C2C03F /* haizeiwang.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = haizeiwang.jpeg; sourceTree = ""; }; 45 | 19F1670420994FD500C2C03F /* CustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomView.swift; sourceTree = ""; }; 46 | 19F167062099505D00C2C03F /* UILabel+VerticalText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+VerticalText.swift"; sourceTree = ""; }; 47 | 19F167082099535200C2C03F /* CustomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomViewController.swift; sourceTree = ""; }; 48 | 19F1670B2099692900C2C03F /* left.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = left.gif; sourceTree = ""; }; 49 | 19F1670C2099692900C2C03F /* poetry.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = poetry.gif; sourceTree = ""; }; 50 | 19F1670D2099692900C2C03F /* reverse.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = reverse.gif; sourceTree = ""; }; 51 | 19F1670E2099692900C2C03F /* right.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = right.gif; sourceTree = ""; }; 52 | 19F1670F2099692900C2C03F /* picture.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = picture.gif; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 19F166E120968DC000C2C03F /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 192AD15C212FD16C0031AD46 /* Sources */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 192AD15D212FD16C0031AD46 /* JXMarqueeView.swift */, 70 | ); 71 | path = Sources; 72 | sourceTree = SOURCE_ROOT; 73 | }; 74 | 19F166DB20968DC000C2C03F = { 75 | isa = PBXGroup; 76 | children = ( 77 | 19F166E620968DC000C2C03F /* JXMarqueeView */, 78 | 19F166E520968DC000C2C03F /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 19F166E520968DC000C2C03F /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 19F166E420968DC000C2C03F /* JXMarqueeView.app */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 19F166E620968DC000C2C03F /* JXMarqueeView */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 192AD15C212FD16C0031AD46 /* Sources */, 94 | 19F166E720968DC000C2C03F /* AppDelegate.swift */, 95 | 19F166E920968DC000C2C03F /* ViewController.swift */, 96 | 19F166FC2098632D00C2C03F /* MarqueeViewController.swift */, 97 | 19F167002098689800C2C03F /* MarqueeImageViewViewController.swift */, 98 | 19F167082099535200C2C03F /* CustomViewController.swift */, 99 | 19F1670420994FD500C2C03F /* CustomView.swift */, 100 | 19F167062099505D00C2C03F /* UILabel+VerticalText.swift */, 101 | 192AD168212FFD660031AD46 /* CustomCopyViewController.swift */, 102 | 192AD16A212FFD780031AD46 /* CustomCopyView.swift */, 103 | 19F166EB20968DC000C2C03F /* Main.storyboard */, 104 | 19F166EE20968DC100C2C03F /* Assets.xcassets */, 105 | 19F166F020968DC100C2C03F /* LaunchScreen.storyboard */, 106 | 19F166F320968DC100C2C03F /* Info.plist */, 107 | 19F1670A2099646C00C2C03F /* Assets */, 108 | ); 109 | path = JXMarqueeView; 110 | sourceTree = ""; 111 | }; 112 | 19F1670A2099646C00C2C03F /* Assets */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 19F1670B2099692900C2C03F /* left.gif */, 116 | 19F1670F2099692900C2C03F /* picture.gif */, 117 | 19F1670C2099692900C2C03F /* poetry.gif */, 118 | 19F1670D2099692900C2C03F /* reverse.gif */, 119 | 19F1670E2099692900C2C03F /* right.gif */, 120 | 19F1670220994B6800C2C03F /* haizeiwang.jpeg */, 121 | ); 122 | path = Assets; 123 | sourceTree = ""; 124 | }; 125 | /* End PBXGroup section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | 19F166E320968DC000C2C03F /* JXMarqueeView */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = 19F166F620968DC100C2C03F /* Build configuration list for PBXNativeTarget "JXMarqueeView" */; 131 | buildPhases = ( 132 | 19F166E020968DC000C2C03F /* Sources */, 133 | 19F166E120968DC000C2C03F /* Frameworks */, 134 | 19F166E220968DC000C2C03F /* Resources */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = JXMarqueeView; 141 | productName = JXMarqueeView; 142 | productReference = 19F166E420968DC000C2C03F /* JXMarqueeView.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 19F166DC20968DC000C2C03F /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastSwiftUpdateCheck = 0930; 152 | LastUpgradeCheck = 0930; 153 | ORGANIZATIONNAME = jiaxin; 154 | TargetAttributes = { 155 | 19F166E320968DC000C2C03F = { 156 | CreatedOnToolsVersion = 9.3; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = 19F166DF20968DC000C2C03F /* Build configuration list for PBXProject "JXMarqueeView" */; 161 | compatibilityVersion = "Xcode 9.3"; 162 | developmentRegion = en; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = 19F166DB20968DC000C2C03F; 169 | productRefGroup = 19F166E520968DC000C2C03F /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | 19F166E320968DC000C2C03F /* JXMarqueeView */, 174 | ); 175 | }; 176 | /* End PBXProject section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | 19F166E220968DC000C2C03F /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | 19F1670320994B6800C2C03F /* haizeiwang.jpeg in Resources */, 184 | 19F166F220968DC100C2C03F /* LaunchScreen.storyboard in Resources */, 185 | 19F167102099692900C2C03F /* left.gif in Resources */, 186 | 19F167132099692900C2C03F /* right.gif in Resources */, 187 | 19F166EF20968DC100C2C03F /* Assets.xcassets in Resources */, 188 | 19F167142099692900C2C03F /* picture.gif in Resources */, 189 | 19F166ED20968DC000C2C03F /* Main.storyboard in Resources */, 190 | 19F167122099692900C2C03F /* reverse.gif in Resources */, 191 | 19F167112099692900C2C03F /* poetry.gif in Resources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXResourcesBuildPhase section */ 196 | 197 | /* Begin PBXSourcesBuildPhase section */ 198 | 19F166E020968DC000C2C03F /* Sources */ = { 199 | isa = PBXSourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 192AD16B212FFD780031AD46 /* CustomCopyView.swift in Sources */, 203 | 19F166FD2098632D00C2C03F /* MarqueeViewController.swift in Sources */, 204 | 192AD169212FFD660031AD46 /* CustomCopyViewController.swift in Sources */, 205 | 19F167012098689800C2C03F /* MarqueeImageViewViewController.swift in Sources */, 206 | 19F167072099505D00C2C03F /* UILabel+VerticalText.swift in Sources */, 207 | 19F1670520994FD500C2C03F /* CustomView.swift in Sources */, 208 | 19F166EA20968DC000C2C03F /* ViewController.swift in Sources */, 209 | 192AD15E212FD16C0031AD46 /* JXMarqueeView.swift in Sources */, 210 | 19F166E820968DC000C2C03F /* AppDelegate.swift in Sources */, 211 | 19F167092099535200C2C03F /* CustomViewController.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 19F166EB20968DC000C2C03F /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 19F166EC20968DC000C2C03F /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | 19F166F020968DC100C2C03F /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 19F166F120968DC100C2C03F /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 19F166F420968DC100C2C03F /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_ENABLE_OBJC_WEAK = YES; 248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_COMMA = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | CODE_SIGN_IDENTITY = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = dwarf; 272 | ENABLE_STRICT_OBJC_MSGSEND = YES; 273 | ENABLE_TESTABILITY = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu11; 275 | GCC_DYNAMIC_NO_PIC = NO; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_OPTIMIZATION_LEVEL = 0; 278 | GCC_PREPROCESSOR_DEFINITIONS = ( 279 | "DEBUG=1", 280 | "$(inherited)", 281 | ); 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 289 | MTL_ENABLE_DEBUG_INFO = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = iphoneos; 292 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | }; 295 | name = Debug; 296 | }; 297 | 19F166F520968DC100C2C03F /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_ENABLE_OBJC_WEAK = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | CODE_SIGN_IDENTITY = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | ENABLE_NS_ASSERTIONS = NO; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu11; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 337 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 338 | GCC_WARN_UNDECLARED_SELECTOR = YES; 339 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 340 | GCC_WARN_UNUSED_FUNCTION = YES; 341 | GCC_WARN_UNUSED_VARIABLE = YES; 342 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 343 | MTL_ENABLE_DEBUG_INFO = NO; 344 | SDKROOT = iphoneos; 345 | SWIFT_COMPILATION_MODE = wholemodule; 346 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 347 | VALIDATE_PRODUCT = YES; 348 | }; 349 | name = Release; 350 | }; 351 | 19F166F720968DC100C2C03F /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 | CODE_SIGN_STYLE = Manual; 356 | DEVELOPMENT_TEAM = ""; 357 | INFOPLIST_FILE = JXMarqueeView/Info.plist; 358 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "@executable_path/Frameworks", 362 | ); 363 | PRODUCT_BUNDLE_IDENTIFIER = jiaxin.JXMarqueeView; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | PROVISIONING_PROFILE_SPECIFIER = ""; 366 | SWIFT_VERSION = 5.0; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Debug; 370 | }; 371 | 19F166F820968DC100C2C03F /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | CODE_SIGN_STYLE = Manual; 376 | DEVELOPMENT_TEAM = ""; 377 | INFOPLIST_FILE = JXMarqueeView/Info.plist; 378 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 379 | LD_RUNPATH_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "@executable_path/Frameworks", 382 | ); 383 | PRODUCT_BUNDLE_IDENTIFIER = jiaxin.JXMarqueeView; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | PROVISIONING_PROFILE_SPECIFIER = ""; 386 | SWIFT_VERSION = 5.0; 387 | TARGETED_DEVICE_FAMILY = "1,2"; 388 | }; 389 | name = Release; 390 | }; 391 | /* End XCBuildConfiguration section */ 392 | 393 | /* Begin XCConfigurationList section */ 394 | 19F166DF20968DC000C2C03F /* Build configuration list for PBXProject "JXMarqueeView" */ = { 395 | isa = XCConfigurationList; 396 | buildConfigurations = ( 397 | 19F166F420968DC100C2C03F /* Debug */, 398 | 19F166F520968DC100C2C03F /* Release */, 399 | ); 400 | defaultConfigurationIsVisible = 0; 401 | defaultConfigurationName = Release; 402 | }; 403 | 19F166F620968DC100C2C03F /* Build configuration list for PBXNativeTarget "JXMarqueeView" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | 19F166F720968DC100C2C03F /* Debug */, 407 | 19F166F820968DC100C2C03F /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Release; 411 | }; 412 | /* End XCConfigurationList section */ 413 | }; 414 | rootObject = 19F166DC20968DC000C2C03F /* Project object */; 415 | } 416 | -------------------------------------------------------------------------------- /JXMarqueeView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JXMarqueeView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JXMarqueeView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/4/30. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /JXMarqueeView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /JXMarqueeView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /JXMarqueeView/Assets/haizeiwang.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/haizeiwang.jpeg -------------------------------------------------------------------------------- /JXMarqueeView/Assets/left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/left.gif -------------------------------------------------------------------------------- /JXMarqueeView/Assets/picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/picture.gif -------------------------------------------------------------------------------- /JXMarqueeView/Assets/poetry.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/poetry.gif -------------------------------------------------------------------------------- /JXMarqueeView/Assets/reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/reverse.gif -------------------------------------------------------------------------------- /JXMarqueeView/Assets/right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujiaxin33/JXMarqueeView/9265a22d5f45bacff2e44a812edfae1a80fb9eea/JXMarqueeView/Assets/right.gif -------------------------------------------------------------------------------- /JXMarqueeView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /JXMarqueeView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /JXMarqueeView/CustomCopyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomCopyView.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/8/24. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomCopyView: UIView { 12 | var circleView: UIView? 13 | var shadowView: UIView? 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | initializeViews() 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | //方案1、实现该方法,拷贝的时候,调用NSKeyedUnarchiver.unarchiveObject(with: archivedData)方法,会调用该方法 24 | initializeViews() 25 | } 26 | 27 | func initializeViews() { 28 | circleView = UIView() 29 | circleView?.backgroundColor = .yellow 30 | circleView?.layer.cornerRadius = 15; 31 | circleView?.layer.masksToBounds = true 32 | addSubview(circleView!) 33 | 34 | shadowView = UIView() 35 | shadowView?.backgroundColor = .green 36 | shadowView?.layer.shadowOpacity = 0.6 37 | shadowView?.layer.shadowColor = UIColor.black.cgColor 38 | shadowView?.layer.shadowOffset = CGSize(width: 3, height: 3) 39 | shadowView?.layer.shadowRadius = 3 40 | addSubview(shadowView!) 41 | 42 | self.backgroundColor = .red 43 | } 44 | 45 | override func layoutSubviews() { 46 | super.layoutSubviews() 47 | 48 | self.layer.cornerRadius = self.bounds.height/2 49 | circleView?.frame = CGRect(x: 10, y: 10, width: 30, height: 30) 50 | shadowView?.frame = CGRect(x: self.bounds.size.width - 30 - 10, y: 10, width: 30, height: 30) 51 | } 52 | 53 | //方案2、如果没有实现required init?(coder aDecoder: NSCoder)方法 54 | //且要对拷贝视图进行特殊操作,就重写该方法进行自定义返回,不能返回自己,要重新生成一份实例 55 | // override func copyMarqueeView() -> UIView { 56 | // return CustomCopyView(frame: self.frame) 57 | // } 58 | } 59 | -------------------------------------------------------------------------------- /JXMarqueeView/CustomCopyViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomCopyViewController.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/8/24. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomCopyViewController: UIViewController { 12 | private let marqueeView = JXMarqueeView() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.view.backgroundColor = UIColor.white 18 | 19 | let customView = CustomCopyView(frame: CGRect(x: 0, y: 100, width: 300, height: 50)) 20 | marqueeView.contentView = customView 21 | marqueeView.marqueeType = .left 22 | marqueeView.pointsPerFrame = 0.2 23 | self.view.addSubview(marqueeView) 24 | } 25 | 26 | override func viewDidLayoutSubviews() { 27 | super.viewDidLayoutSubviews() 28 | 29 | marqueeView.bounds = CGRect(x:0, y:0, width: self.view.bounds.width - 200, height: 50) 30 | marqueeView.center = self.view.center 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /JXMarqueeView/CustomView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomView.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/5/2. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomView: UIView{ 12 | var poetry: [String]? 13 | 14 | init() { 15 | super.init(frame: CGRect.zero) 16 | 17 | poetry = ["君不见,黄河之水天上来,奔流到海不复回。", 18 | "君不见,高堂明镜悲白发,朝如青丝暮成雪。", 19 | "人生得意须尽欢,莫使金樽空对月。", 20 | "天生我材必有用,千金散尽还复来。", 21 | "烹羊宰牛且为乐,会须一饮三百杯。", 22 | "岑夫子,丹丘生,将进酒,杯莫停。", 23 | "与君歌一曲,请君为我倾耳听。", 24 | "钟鼓馔玉不足贵,但愿长醉不复醒。", 25 | "古来圣贤皆寂寞,惟有饮者留其名。", 26 | "陈王昔时宴平乐,斗酒十千恣欢谑。", 27 | "主人何为言少钱,径须沽取对君酌。", 28 | "五花马,千金裘,呼儿将出换美酒,与尔同销万古愁。",] 29 | 30 | for (index,verse) in poetry!.enumerated() { 31 | let label = UILabel() 32 | label.configVerticalText(verticalText: verse) 33 | label.textColor = UIColor.black 34 | label.frame = CGRect(x: CGFloat(index + 1)*50, y: 0, width: 30, height: 500) 35 | self.addSubview(label) 36 | } 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | super.init(coder: aDecoder) 41 | } 42 | 43 | override func sizeThatFits(_ size: CGSize) -> CGSize { 44 | return CGSize(width: CGFloat(poetry!.count + 2)*50, height: size.height) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /JXMarqueeView/CustomViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/5/2. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomViewController: UIViewController { 12 | private let marqueeView = JXMarqueeView() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.view.backgroundColor = UIColor.white 18 | 19 | let view = CustomView() 20 | marqueeView.contentView = view 21 | marqueeView.marqueeType = .reverse 22 | marqueeView.pointsPerFrame = 0.2 23 | self.view.addSubview(marqueeView) 24 | } 25 | 26 | override func viewDidLayoutSubviews() { 27 | super.viewDidLayoutSubviews() 28 | 29 | marqueeView.bounds = CGRect(x:0, y:0, width: self.view.bounds.width, height: 500) 30 | marqueeView.center = self.view.center 31 | } 32 | 33 | 34 | /* 35 | // MARK: - Navigation 36 | 37 | // In a storyboard-based application, you will often want to do a little preparation before navigation 38 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 39 | // Get the new view controller using segue.destinationViewController. 40 | // Pass the selected object to the new view controller. 41 | } 42 | */ 43 | 44 | } 45 | -------------------------------------------------------------------------------- /JXMarqueeView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /JXMarqueeView/MarqueeImageViewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarqueeImageViewViewController.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/5/1. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MarqueeImageViewViewController: UIViewController { 12 | private let marqueeView = JXMarqueeView() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.view.backgroundColor = UIColor.white 18 | 19 | let imageView = UIImageView(image: UIImage(named: "haizeiwang.jpeg")) 20 | imageView.contentMode = .scaleAspectFill 21 | 22 | marqueeView.contentView = imageView 23 | marqueeView.marqueeType = .reverse 24 | self.view.addSubview(marqueeView) 25 | } 26 | 27 | override func viewDidLayoutSubviews() { 28 | super.viewDidLayoutSubviews() 29 | 30 | marqueeView.bounds = CGRect(x:0, y:0, width: self.view.bounds.width, height: 360) 31 | marqueeView.center = self.view.center 32 | } 33 | 34 | 35 | /* 36 | // MARK: - Navigation 37 | 38 | // In a storyboard-based application, you will often want to do a little preparation before navigation 39 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 40 | // Get the new view controller using segue.destinationViewController. 41 | // Pass the selected object to the new view controller. 42 | } 43 | */ 44 | 45 | } 46 | -------------------------------------------------------------------------------- /JXMarqueeView/MarqueeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarqueeViewController.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/5/1. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MarqueeViewController: UIViewController { 12 | var marqueeType: JXMarqueeType? 13 | private let marqueeView = JXMarqueeView() 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | self.view.backgroundColor = UIColor.white 19 | 20 | let label = UILabel() 21 | label.textColor = UIColor.red 22 | label.font = UIFont.systemFont(ofSize: 30, weight: .medium) 23 | label.text = "abcdefghijklmnopqrstuvwxyz" 24 | 25 | marqueeView.contentView = label 26 | marqueeView.backgroundColor = UIColor.lightGray 27 | marqueeView.contentMargin = 50 28 | marqueeView.marqueeType = marqueeType! 29 | self.view.addSubview(marqueeView) 30 | } 31 | 32 | override func viewDidLayoutSubviews() { 33 | super.viewDidLayoutSubviews() 34 | 35 | marqueeView.bounds = CGRect(x:0, y:0, width: 200, height: 60) 36 | marqueeView.center = self.view.center 37 | } 38 | 39 | override func didReceiveMemoryWarning() { 40 | super.didReceiveMemoryWarning() 41 | // Dispose of any resources that can be recreated. 42 | } 43 | 44 | 45 | /* 46 | // MARK: - Navigation 47 | 48 | // In a storyboard-based application, you will often want to do a little preparation before navigation 49 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 50 | // Get the new view controller using segue.destinationViewController. 51 | // Pass the selected object to the new view controller. 52 | } 53 | */ 54 | 55 | } 56 | -------------------------------------------------------------------------------- /JXMarqueeView/UILabel+VerticalText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+VerticalText.swift 3 | // JXMarqueeView 4 | // 5 | // Created by jiaxin on 2018/5/2. 6 | // Copyright © 2018年 jiaxin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UILabel { 12 | func configVerticalText(verticalText: String) { 13 | var tempString = verticalText 14 | for index in 1..' do 43 | pod 'JXMarqueeView' 44 | end 45 | ``` 46 | 47 | # Usage 48 | 49 | - **contentMargin** 50 | 51 | The interval between two views,default is 12. 52 | 53 | - **frameInterval** 54 | 55 | Assiagned to CADisplayLink frameInterval property,default is 1. 56 | 57 | - **pointsPerFrame** 58 | 59 | How many points each time for callback of CADisplayLink.The bigger the faster. 60 | 61 | - **contentView** 62 | 63 | The view your need to marquee. 64 | 65 | - **SizeToFit** 66 | 67 | When you customize complex content view, you need override `func sizeThatFits(_ size: CGSize) -> CGSize`,and return you correct content size. 68 | 69 | ## Use case 70 | ```swift 71 | //text 72 | let label = UILabel() 73 | label.textColor = UIColor.red 74 | label.font = UIFont.systemFont(ofSize: 30, weight: .medium) 75 | label.text = "abcdefghijklmnopqrstuvwxyz" 76 | 77 | marqueeView.contentView = label 78 | marqueeView.contentMargin = 50 79 | marqueeView.marqueeType = .left 80 | self.view.addSubview(marqueeView) 81 | 82 | //picture 83 | let imageView = UIImageView(image: UIImage(named: "haizeiwang.jpeg")) 84 | imageView.contentMode = .scaleAspectFill 85 | 86 | marqueeView.contentView = imageView 87 | marqueeView.marqueeType = .reverse 88 | self.view.addSubview(marqueeView) 89 | ``` 90 | 91 | ## Customize 92 | 93 | The default implementation of contentView's copy using code: 94 | ``` 95 | let archivedData = NSKeyedArchiver.archivedData(withRootObject: self) 96 | let copyView = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as! UIView 97 | ``` 98 | But if the view has cornerRadius、shadow, the copyView will lose it. So you should implement `protocol JXMarqueeViewCopyable` function `func copyMarqueeView() -> UIView`. Just return a new instance UIView. 99 | Just checkout `CustomCopyView.swift` in example. 100 | 101 | ### Picture case preview 102 | ![picture.gif](https://github.com/pujiaxin33/JXMarqueeView/blob/master/JXMarqueeView/Assets/picture.gif?raw=true) 103 | 104 | ### Custom case preview 105 | ![poetry.gif](https://upload-images.jianshu.io/upload_images/1085173-c197188ee4e4fb44.gif?imageMogr2/auto-orient/strip) 106 | -------------------------------------------------------------------------------- /Sources/JXMarqueeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JXMarqueeView.swift 3 | // DQGuess 4 | // 5 | // Created by jiaxin on 2018/4/27. 6 | // Copyright © 2018年 jingbo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol JXMarqueeViewCopyable { 12 | /// 如果视图里面有圆角、阴影等,仅通过NSKeyedArchiver、NSKeyedUnarchiver相关方法,会丢失对应信息。所以,这种特殊情况需要自定义返回。 13 | /// 重新拷贝一份目标视图。不能返回视图自己,需要重新创建一个实例。 14 | /// 第一种方案,实现required init?(coder aDecoder: NSCoder) 初始化器,返回一个新实例。参考CustomCopyView 15 | /// 第二种方案,重载func copyMarqueeView() -> UIView方法,返回一个新实例。参考CustomCopyView 16 | /// 17 | /// - Returns: new view 18 | func copyMarqueeView() -> UIView 19 | } 20 | 21 | extension UIView: JXMarqueeViewCopyable { 22 | @objc open func copyMarqueeView() -> UIView { 23 | //UIView是没有遵从拷贝协议的。可以通过UIView支持NSCoding协议,间接来复制一个视图 24 | let archivedData = NSKeyedArchiver.archivedData(withRootObject: self) 25 | let copyView = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as! UIView 26 | return copyView 27 | } 28 | } 29 | 30 | public enum JXMarqueeType { 31 | case left 32 | case right 33 | case reverse 34 | } 35 | 36 | public class JXMarqueeView: UIView { 37 | public var marqueeType: JXMarqueeType = .left 38 | public var contentMargin: CGFloat = 12 //两个视图之间的间隔 39 | public var preferredFramesPerSecond: Int = 0 //多少帧回调一次,普通设备是一帧时间1/60秒,高刷频设备默认为设备自身的刷新率 40 | public var pointsPerFrame: CGFloat = 0.5 //每次回调移动多少点 41 | public var contentView: UIView? { 42 | didSet { 43 | self.setNeedsLayout() 44 | } 45 | } 46 | public var contentViewFrameConfigWhenCantMarquee: ((UIView)->())? //当contentView的内容宽度没有超过显示宽度,无需开启跑马灯效果。这个时候contentView的size,默认是调用sizeToFit之后的尺寸。如果想要特殊配置,比如让contentView的size等于JXMarqueeView,就需要在该闭包自定义配置。 47 | private let containerView = UIView() 48 | private var marqueeDisplayLink: CADisplayLink? 49 | private var isReversing = false 50 | 51 | override open func willMove(toSuperview newSuperview: UIView?) { 52 | //当视图将被移除父视图的时候,newSuperview就为nil。在这个时候,停止掉CADisplayLink,断开循环引用,视图就可以被正确释放掉了。 53 | if newSuperview == nil { 54 | self.stopMarquee() 55 | } 56 | } 57 | 58 | public init() { 59 | super.init(frame: CGRect.zero) 60 | 61 | self.initializeViews() 62 | } 63 | 64 | override public init(frame: CGRect) { 65 | super.init(frame: frame) 66 | 67 | self.initializeViews() 68 | } 69 | 70 | required public init?(coder aDecoder: NSCoder) { 71 | super.init(coder: aDecoder) 72 | 73 | self.initializeViews() 74 | } 75 | 76 | func initializeViews() { 77 | self.backgroundColor = UIColor.clear 78 | self.clipsToBounds = true 79 | 80 | containerView.backgroundColor = UIColor.clear 81 | self.addSubview(containerView) 82 | } 83 | 84 | override open func layoutSubviews() { 85 | super.layoutSubviews() 86 | 87 | guard let validContentView = contentView else { 88 | return 89 | } 90 | containerView.subviews.forEach {$0.removeFromSuperview() } 91 | 92 | //对于复杂的视图,需要自己重写contentView的sizeThatFits方法,返回正确的size 93 | validContentView.sizeToFit() 94 | containerView.addSubview(validContentView) 95 | 96 | if marqueeType == .reverse { 97 | containerView.frame = CGRect(x: 0, y: 0, width: validContentView.bounds.size.width, height: self.bounds.size.height) 98 | }else { 99 | containerView.frame = CGRect(x: 0, y: 0, width: validContentView.bounds.size.width*2 + contentMargin, height: self.bounds.size.height) 100 | } 101 | 102 | if validContentView.bounds.size.width > self.bounds.size.width { 103 | validContentView.frame = CGRect(x: 0, y: 0, width: validContentView.bounds.size.width, height: self.bounds.size.height) 104 | if marqueeType != .reverse { 105 | let otherContentView = validContentView.copyMarqueeView() 106 | otherContentView.frame = CGRect(x: validContentView.bounds.size.width + contentMargin, y: 0, width: validContentView.bounds.size.width, height: self.bounds.size.height) 107 | containerView.addSubview(otherContentView) 108 | } 109 | 110 | if self.bounds.size.width != 0 { 111 | self.startMarquee() 112 | } 113 | }else { 114 | if contentViewFrameConfigWhenCantMarquee != nil { 115 | contentViewFrameConfigWhenCantMarquee!(validContentView) 116 | }else { 117 | validContentView.frame = CGRect(x: 0, y: 0, width: validContentView.bounds.size.width, height: self.bounds.size.height) 118 | } 119 | } 120 | } 121 | 122 | //如果你的contentView的内容在初始化的时候,无法确定。需要通过网络等延迟获取,那么在内容赋值之后,在调用该方法即可。 123 | public func reloadData() { 124 | self.setNeedsLayout() 125 | } 126 | 127 | fileprivate func startMarquee() { 128 | self.stopMarquee() 129 | 130 | if marqueeType == .right { 131 | var frame = self.containerView.frame 132 | frame.origin.x = self.bounds.size.width - frame.size.width 133 | self.containerView.frame = frame 134 | } 135 | 136 | self.marqueeDisplayLink = CADisplayLink.init(target: self, selector: #selector(processMarquee)) 137 | self.marqueeDisplayLink?.preferredFramesPerSecond = self.preferredFramesPerSecond 138 | self.marqueeDisplayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 139 | } 140 | 141 | fileprivate func stopMarquee() { 142 | self.marqueeDisplayLink?.invalidate() 143 | self.marqueeDisplayLink = nil 144 | } 145 | 146 | @objc fileprivate func processMarquee() { 147 | var frame = self.containerView.frame 148 | 149 | switch marqueeType { 150 | case .left: 151 | let targetX = -(self.contentView!.bounds.size.width + self.contentMargin) 152 | if frame.origin.x <= targetX { 153 | frame.origin.x = 0 154 | self.containerView.frame = frame 155 | }else { 156 | frame.origin.x -= pointsPerFrame 157 | if frame.origin.x < targetX { 158 | frame.origin.x = targetX 159 | } 160 | self.containerView.frame = frame 161 | } 162 | case .right: 163 | let targetX = self.bounds.size.width - self.contentView!.bounds.size.width 164 | if frame.origin.x >= targetX { 165 | frame.origin.x = self.bounds.size.width - self.containerView.bounds.size.width 166 | self.containerView.frame = frame 167 | }else { 168 | frame.origin.x += pointsPerFrame 169 | if frame.origin.x > targetX { 170 | frame.origin.x = targetX 171 | } 172 | self.containerView.frame = frame 173 | } 174 | case .reverse: 175 | if isReversing { 176 | let targetX: CGFloat = 0 177 | if frame.origin.x > targetX { 178 | frame.origin.x = 0 179 | self.containerView.frame = frame 180 | isReversing = false 181 | }else { 182 | frame.origin.x += pointsPerFrame 183 | if frame.origin.x > 0 { 184 | frame.origin.x = 0 185 | isReversing = false 186 | } 187 | self.containerView.frame = frame 188 | } 189 | }else { 190 | let targetX = self.bounds.size.width - self.containerView.bounds.size.width 191 | if frame.origin.x <= targetX { 192 | isReversing = true 193 | }else { 194 | frame.origin.x -= pointsPerFrame 195 | if frame.origin.x < targetX { 196 | frame.origin.x = targetX 197 | isReversing = true 198 | } 199 | self.containerView.frame = frame 200 | } 201 | } 202 | } 203 | 204 | } 205 | 206 | } 207 | --------------------------------------------------------------------------------