33// copy, modify, and distribute this software in source code or binary form for use
44// in connection with the web services and APIs provided by Rover.
55//
6- // This copyright notice shall be included in all copies or substantial portions of
6+ // This copyright notice shall be included in all copies or substantial portions of
77// the software.
88//
99// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
@@ -23,11 +23,11 @@ class ImageStore {
2323 // MARK: Cache
2424
2525 fileprivate enum Optimization : Equatable , Hashable {
26- case fill( bounds: CGRect )
27- case fit( bounds: CGRect )
28- case stretch( bounds: CGRect , originalSize: CGSize )
29- case original( bounds: CGRect , originalSize: CGSize , originalScale: CGFloat )
30- case tile( bounds: CGRect , originalSize: CGSize , originalScale: CGFloat )
26+ case fill( bounds: HashableCGRect )
27+ case fit( bounds: HashableCGRect )
28+ case stretch( bounds: HashableCGRect , originalSize: HashableCGSize )
29+ case original( bounds: HashableCGRect , originalSize: HashableCGSize , originalScale: CGFloat )
30+ case tile( bounds: HashableCGRect , originalSize: HashableCGSize , originalScale: CGFloat )
3131 }
3232
3333 fileprivate struct Configuration : Equatable , Hashable {
@@ -74,8 +74,9 @@ class ImageStore {
7474 let configuration = Configuration ( image: image, frame: frame)
7575 return self . image ( for: configuration)
7676 }
77-
77+
7878 func image( for image: ClassicImage , filledInFrame frame: CGRect ) -> UIImage ? {
79+ let frame = HashableCGRect ( frame)
7980 let optimization : ImageStore . Optimization = . fill( bounds: frame)
8081 let configuration = Configuration ( url: image. url, optimization: optimization)
8182 return self . image ( for: configuration)
@@ -115,6 +116,7 @@ class ImageStore {
115116 }
116117
117118 func fetchImage( for image: ClassicImage , filledInFrame frame: CGRect , completionHandler: ( ( UIImage ? ) -> Void ) ? = nil ) {
119+ let frame = HashableCGRect ( frame)
118120 let optimization : ImageStore . Optimization = . fill( bounds: frame)
119121 let configuration = Configuration ( url: image. url, optimization: optimization)
120122 fetchImage ( for: configuration, completionHandler: completionHandler)
@@ -193,11 +195,12 @@ extension ImageStore.Configuration {
193195 guard let image = background. image else {
194196 return nil
195197 }
196-
198+
199+ let frame = HashableCGRect ( frame)
197200 let optimization : ImageStore . Optimization ?
198201 if image. isURLOptimizationEnabled {
199- let originalSize = CGSize ( width: CGFloat ( image. width) , height: CGFloat ( image. height) )
200-
202+ let originalSize = HashableCGSize ( width: CGFloat ( image. width) , height: CGFloat ( image. height) )
203+
201204 let originalScale : CGFloat
202205 switch background. scale {
203206 case . x1:
@@ -228,7 +231,8 @@ extension ImageStore.Configuration {
228231 }
229232
230233 init ( image: ClassicImage , frame: CGRect ) {
231- let originalSize = CGSize ( width: CGFloat ( image. width) , height: CGFloat ( image. height) )
234+ let frame = HashableCGRect ( frame)
235+ let originalSize = HashableCGSize ( width: CGFloat ( image. width) , height: CGFloat ( image. height) )
232236 let optimization = ImageStore . Optimization. stretch ( bounds: frame, originalSize: originalSize)
233237 self . init ( url: image. url, optimization: optimization)
234238 }
@@ -292,24 +296,92 @@ extension ImageStore.Optimization {
292296 }
293297}
294298
295- fileprivate extension CGFloat {
296- var paramValue : String {
299+ extension CGFloat {
300+ fileprivate var paramValue : String {
297301 let rounded = self . rounded ( )
298302 let int = Int ( rounded)
299303 return int. description
300304 }
301305}
302306
303- extension CGSize : Hashable {
304- public func hash( into hasher: inout Hasher ) {
305- hasher. combine ( width)
306- hasher. combine ( height)
307+ /// Wrapper type providing Hashable and Sendable conformance for CGSize without extending public CoreGraphics types (to avoid public extension conflicts and ABI/compatibility issues). Offers conversion helper (cgSize) for interop.
308+ fileprivate struct HashableCGSize : Hashable , Sendable {
309+ var width : CGFloat
310+ var height : CGFloat
311+
312+ init ( _ size: CGSize ) {
313+ self . init ( width: size. width, height: size. height)
314+ }
315+
316+ init ( width: CGFloat , height: CGFloat ) {
317+ self . width = width
318+ self . height = height
319+ }
320+
321+ var cgSize : CGSize {
322+ CGSize ( width: width, height: height)
323+ }
324+
325+ func hash( into hasher: inout Hasher ) {
326+ hasher. combine ( Double ( width) )
327+ hasher. combine ( Double ( height) )
328+ }
329+
330+ static func == ( lhs: HashableCGSize , rhs: HashableCGSize ) -> Bool {
331+ lhs. width == rhs. width && lhs. height == rhs. height
307332 }
308333}
309334
310- extension CGRect : Hashable {
311- public func hash( into hasher: inout Hasher ) {
335+ /// Wrapper type providing Hashable and Sendable conformance for CGRect without extending public CoreGraphics types (to avoid public extension conflicts and ABI/compatibility issues). Offers conversion helper (cgRect) for interop.
336+ fileprivate struct HashableCGRect : Hashable , Sendable {
337+ var origin : HashableCGPoint
338+ var size : HashableCGSize
339+
340+ init ( _ rect: CGRect ) {
341+ self . origin = HashableCGPoint ( rect. origin)
342+ self . size = HashableCGSize ( rect. size)
343+ }
344+
345+ var cgRect : CGRect {
346+ CGRect ( origin: origin. cgPoint, size: size. cgSize)
347+ }
348+
349+ var width : CGFloat {
350+ size. width
351+ }
352+
353+ var height : CGFloat {
354+ size. height
355+ }
356+
357+ func hash( into hasher: inout Hasher ) {
312358 hasher. combine ( origin)
313359 hasher. combine ( size)
314360 }
361+
362+ static func == ( lhs: Self , rhs: Self ) -> Bool {
363+ lhs. origin == rhs. origin && lhs. size == rhs. size
364+ }
365+ }
366+
367+ /// Wrapper type providing Hashable and Sendable conformance for CGPoint without extending public CoreGraphics types (to avoid public extension conflicts and ABI/compatibility issues). Offers conversion helper (cgPoint) for interop.
368+ fileprivate struct HashableCGPoint : Hashable , Sendable {
369+ var x : CGFloat
370+ var y : CGFloat
371+
372+ init ( _ point: CGPoint ) {
373+ self . x = point. x
374+ self . y = point. y
375+ }
376+
377+ var cgPoint : CGPoint { CGPoint ( x: x, y: y) }
378+
379+ func hash( into hasher: inout Hasher ) {
380+ hasher. combine ( Double ( x) )
381+ hasher. combine ( Double ( y) )
382+ }
383+
384+ static func == ( lhs: Self , rhs: Self ) -> Bool {
385+ lhs. x == rhs. x && lhs. y == rhs. y
386+ }
315387}
0 commit comments