diff --git a/README.md b/README.md index 4a82436..b795369 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,17 @@ To add RatingView to your Storyboard/.xib file just drag a generic UIView from p ## Installation +### Carthage +- `cd` to your project folder +- `touch Cartfile` (if you don't have one yet) +- `nano Cartfile` +- put `github "peterprokop/StarryStars" == 2.0.0` into Cartfile +- Save it: `ctrl-x, y, enter` +- Run `carthage update` +- Copy framework from `Carthage/Build/iOS` to your project +- Make sure that framework is added in Embedded Binaries section of your target (or else you will get dyld library not loaded referenced from ... reason image not found error) +- Add `import StarryStars` on top of your view controller's code + ### Manual Just clone and add ```StarryStars``` directory to your project. @@ -27,7 +38,7 @@ Just clone and add ```StarryStars``` directory to your project. - `nano Podfile`, add: ``` -pod 'StarryStars', '~> 0.0.1' +pod 'StarryStars', '~> 2.0.0' use_frameworks! ``` - Save it: `ctrl-x`, `y`, `enter` @@ -37,9 +48,39 @@ use_frameworks! ## Requirements -- iOS 8.0+ -- Xcode 8.0+ -- Swift 3.0 (for older versions, see `swift-2.2` branch) +- iOS 10.0+ +- Xcode 10.0+ +- Swift 5.0 (for older versions, see `swift-2.2` branch) + +## Usage from code + +Swift: +``` +let rvRightToLeft = RatingView() + +rvRightToLeft.frame = view.bounds + +view.addSubview(rvRightToLeft) +rvRightToLeft.editable = true +rvRightToLeft.delegate = self + +// RatingView will respect setting this property +rvRightToLeft.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft; +``` + +Objective C: +``` +RatingView* rvRightToLeft = [[RatingView alloc] init]; + +rvRightToLeft.frame = self.view.bounds; + +[self.view addSubview:rvRightToLeft]; +rvRightToLeft.editable = YES; +rvRightToLeft.delegate = self; + +// RatingView will respect setting this property +rvRightToLeft.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft; +``` ## Other Projects diff --git a/StarryStars.podspec b/StarryStars.podspec index 50ecf85..54a197e 100644 --- a/StarryStars.podspec +++ b/StarryStars.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = 'StarryStars' - s.version = '1.0.0' + s.version = '2.0.0' s.license = 'MIT' s.summary = 'iOS GUI library for displaying and editing ratings' s.homepage = 'https://github.com/peterprokop/StarryStars' s.authors = { 'Peter Prokop' => 'prokop.petr@gmail.com' } s.source = { :git => 'https://github.com/peterprokop/StarryStars.git', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '10.0' s.requires_arc = 'true' s.source_files = 'StarryStars/*.swift' s.resources = [ "StarryStars/Resource/*.*" ] diff --git a/StarryStars/RatingView.swift b/StarryStars/RatingView.swift index 7b56b08..f2eddce 100644 --- a/StarryStars/RatingView.swift +++ b/StarryStars/RatingView.swift @@ -36,7 +36,7 @@ open class RatingView: UIView { @IBInspectable open var halfImage: UIImage? /// Current rating, updates star images after setting - @IBInspectable open var rating: Float = Float(0) { + @IBInspectable open var rating: Float = 0 { didSet { // If rating is more than starCount simply set it to starCount rating = min(Float(starCount), rating) @@ -50,14 +50,23 @@ open class RatingView: UIView { /// If set to "false" user will not be able to edit the rating @IBInspectable open var editable: Bool = true - - + /// Delegate, must confrom to *RatingViewDelegate* protocol - open weak var delegate: RatingViewDelegate? - - var stars = [UIImageView]() - + @objc open weak var delegate: RatingViewDelegate? + var stars: [UIImageView] = [] + + override open var semanticContentAttribute: UISemanticContentAttribute { + didSet { + updateTransform() + } + } + + private var shouldUseRightToLeft: Bool { + return UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) + == .rightToLeft + } + override init(frame: CGRect) { super.init(frame: frame) @@ -109,6 +118,8 @@ open class RatingView: UIView { layoutStars() updateRating() + + updateTransform() } override open func layoutSubviews() { @@ -116,21 +127,28 @@ open class RatingView: UIView { layoutStars() } + + private func updateTransform() { + transform = shouldUseRightToLeft + ? CGAffineTransform.init(scaleX: -1, y: 1) + : .identity + } - func layoutStars() { - if stars.count != 0, - let offImage = stars.first?.image { - let halfWidth = offImage.size.width/2 - let distance = (bounds.size.width - (offImage.size.width * CGFloat(starCount))) / CGFloat(starCount + 1) + halfWidth - - var i = 1 - for iv in stars { - iv.frame = CGRect(x: 0, y: 0, width: offImage.size.width, height: offImage.size.height) - - iv.center = CGPoint(x: CGFloat(i) * distance + halfWidth * CGFloat(i - 1), - y: self.frame.size.height/2) - i += 1 - } + private func layoutStars() { + guard !stars.isEmpty, let offImage = stars.first?.image else { + return + } + + let halfWidth = offImage.size.width/2 + let distance = (bounds.size.width - (offImage.size.width * CGFloat(starCount))) / CGFloat(starCount + 1) + halfWidth + + for (index, iv) in stars.enumerated() { + iv.frame = CGRect(x: 0, y: 0, width: offImage.size.width, height: offImage.size.height) + + iv.center = CGPoint( + x: CGFloat(index + 1) * distance + halfWidth * CGFloat(index), + y: self.frame.size.height/2 + ) } } @@ -140,7 +158,7 @@ open class RatingView: UIView { func handleTouches(_ touches: Set) { let touch = touches.first! let touchLocation = touch.location(in: self) - + var i = starCount - 1 while i >= 0 { let imageView = stars[i] @@ -166,7 +184,7 @@ open class RatingView: UIView { */ func updateRating() { // To avoid crash when using IB - if stars.count == 0 { + guard !stars.isEmpty else { return } @@ -217,3 +235,11 @@ extension RatingView { delegate.ratingView(self, didChangeRating: rating) } } + +@objc extension RatingView { + @objc public enum FillDirection: Int { + case automatic + case leftToRight + case rightToLeft + } +} diff --git a/StarryStarsExample.xcodeproj/project.pbxproj b/StarryStarsExample.xcodeproj/project.pbxproj index 9614f86..27de8e3 100644 --- a/StarryStarsExample.xcodeproj/project.pbxproj +++ b/StarryStarsExample.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 0D39D5D51E23CD450014489F /* starryStars_half@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0D98B9B51BE60BDB00D89929 /* starryStars_half@2x.png */; }; 0D39D5D61E23CD480014489F /* starryStars_off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0D98B9B61BE60BDB00D89929 /* starryStars_off@2x.png */; }; 0D39D5D71E23CD4B0014489F /* starryStars_on@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0D98B9B71BE60BDB00D89929 /* starryStars_on@2x.png */; }; + 2FE9711B23648E62002212C5 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 2FE9711A23648E62002212C5 /* README.md */; }; + 2FE9711D23649023002212C5 /* StarryStars.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 2FE9711C23649023002212C5 /* StarryStars.podspec */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +64,8 @@ 0D98B9B61BE60BDB00D89929 /* starryStars_off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "starryStars_off@2x.png"; sourceTree = ""; }; 0D98B9B71BE60BDB00D89929 /* starryStars_on@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "starryStars_on@2x.png"; sourceTree = ""; }; 0DEBD39B1BDA134D00FC7856 /* StarryStarsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StarryStarsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2FE9711A23648E62002212C5 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 2FE9711C23649023002212C5 /* StarryStars.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = StarryStars.podspec; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -122,6 +126,8 @@ 0DEBD3921BDA134D00FC7856 = { isa = PBXGroup; children = ( + 2FE9711A23648E62002212C5 /* README.md */, + 2FE9711C23649023002212C5 /* StarryStars.podspec */, 0D2DE71C1BE5F83C005DBFEC /* StarryStars */, 0D2DE7081BE5F23E005DBFEC /* StarryStarsExample */, 0DEBD39C1BDA134D00FC7856 /* Products */, @@ -195,7 +201,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0710; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Peter Prokop"; TargetAttributes = { 0D39D5C61E23CD0F0014489F = { @@ -210,7 +216,7 @@ }; buildConfigurationList = 0DEBD3961BDA134D00FC7856 /* Build configuration list for PBXProject "StarryStarsExample" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -242,6 +248,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2FE9711B23648E62002212C5 /* README.md in Resources */, + 2FE9711D23649023002212C5 /* StarryStars.podspec in Resources */, 0D2DE7161BE5F23E005DBFEC /* Main.storyboard in Resources */, 0D2DE7151BE5F23E005DBFEC /* LaunchScreen.storyboard in Resources */, ); @@ -311,13 +319,13 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = StarryStars/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = Prokop.StarryStars; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -337,12 +345,12 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = StarryStars/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = Prokop.StarryStars; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -357,14 +365,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -403,14 +419,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -442,12 +466,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = StarryStarsExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = Prokop.StarryStarsExample; PRODUCT_NAME = StarryStarsExample; SWIFT_OBJC_BRIDGING_HEADER = "StarryStarsExample/StarryStarsExample-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -458,11 +483,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = StarryStarsExample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = Prokop.StarryStarsExample; PRODUCT_NAME = StarryStarsExample; SWIFT_OBJC_BRIDGING_HEADER = "StarryStarsExample/StarryStarsExample-Bridging-Header.h"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -476,6 +502,7 @@ 0D39D5D11E23CD0F0014489F /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 0DEBD3961BDA134D00FC7856 /* Build configuration list for PBXProject "StarryStarsExample" */ = { isa = XCConfigurationList; diff --git a/StarryStarsExample/AppDelegate.swift b/StarryStarsExample/AppDelegate.swift index 0dbb8d7..cef706b 100644 --- a/StarryStarsExample/AppDelegate.swift +++ b/StarryStarsExample/AppDelegate.swift @@ -12,8 +12,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } @@ -40,6 +39,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } diff --git a/StarryStarsExample/Base.lproj/LaunchScreen.storyboard b/StarryStarsExample/Base.lproj/LaunchScreen.storyboard index b5e2749..45c355d 100644 --- a/StarryStarsExample/Base.lproj/LaunchScreen.storyboard +++ b/StarryStarsExample/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,12 @@ - - + + + + + - + + + @@ -13,10 +18,9 @@ - + - - + diff --git a/StarryStarsExample/ObjCViewController.m b/StarryStarsExample/ObjCViewController.m index 690ecd5..da15033 100644 --- a/StarryStarsExample/ObjCViewController.m +++ b/StarryStarsExample/ObjCViewController.m @@ -19,13 +19,33 @@ @implementation ObjCViewController - (void)viewDidLoad { [super viewDidLoad]; - RatingView* rv = [[RatingView alloc] init]; - rv.frame = self.view.bounds; + rv.frame = CGRectMake( + self.view.bounds.origin.x, + self.view.bounds.origin.y, + self.view.bounds.size.width, + self.view.bounds.size.height/2 + ); [self.view addSubview:rv]; rv.editable = YES; rv.delegate = self; + + RatingView* rvRightToLeft = [[RatingView alloc] init]; + + rvRightToLeft.frame = CGRectMake( + self.view.bounds.origin.x, + self.view.bounds.origin.y + self.view.bounds.size.height/2, + self.view.bounds.size.width, + self.view.bounds.size.height/2 + ); + + [self.view addSubview:rvRightToLeft]; + rvRightToLeft.editable = YES; + rvRightToLeft.delegate = self; + + // RatingView will respect setting this property + rvRightToLeft.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft; } - (void)didReceiveMemoryWarning { @@ -33,7 +53,7 @@ - (void)didReceiveMemoryWarning { } - (void)ratingView:(RatingView * __nonnull)ratingView didChangeRating:(float)newRating { - NSLog(@"newRating: %f", newRating); + NSLog(@"ratingView %@ didChangeRating: %.1f", ratingView, newRating); } diff --git a/StarryStarsExample/ViewController.swift b/StarryStarsExample/ViewController.swift index bb6c70d..37041f9 100644 --- a/StarryStarsExample/ViewController.swift +++ b/StarryStarsExample/ViewController.swift @@ -6,19 +6,31 @@ // import UIKit +import StarryStars class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. + let rvRightToLeft = RatingView() + + rvRightToLeft.frame = view.bounds + + view.addSubview(rvRightToLeft) + rvRightToLeft.editable = true + rvRightToLeft.delegate = self + + // RatingView will respect setting this property + rvRightToLeft.semanticContentAttribute = .forceRightToLeft } +} + +extension ViewController: RatingViewDelegate { + func ratingView(_ ratingView: RatingView, didChangeRating newRating: Float) { + print("ratingView \(ratingView) didChangeRating \(newRating)") + } }