Search code examples
swiftswiftuiiconssymbolsshapes

How can I create a custom dynamic Shape in SwiftUI?


One aspect of my Swift app project is to build a feed with "posts" as I have sketched here.

enter image description here

I have every aspect of this finished except for the date display in the top left corner. How do I build this kind of hexagon shape in Swift UI?

Note that the "date" texts need to dynamic and parametizable. They can't be hard-coded into the shape.


Solution

  • You can use Shape for this project, but since we have SF symbol for what you need, it is easier to use SF symbol. Here one way of doing, you can scale it or change the Date as you like.

    struct ContentView: View {
        
        var body: some View {
            
            CustomDateView(month: "April", day: "01", time: "00:00 AM", scaleEffect: 0.8)
            
            CustomDateView(month: "July", day: "15", time: "2:00 PM", backgroundColor: .blue)
            
            CustomDateView(month: "May", day: "26", time: "8:00 AM", scaleEffect: 0.5, backgroundColor: .green)
            
        }
        
    }
    
    
    struct CustomDateView: View {
        
        let month: String
        let day: String
        let time: String
        let scaleEffect: CGFloat
        let backgroundColor: Color
        
        internal init(month: String, day: String, time: String, scaleEffect: CGFloat = 1.0, backgroundColor: Color = .clear) {
            
            self.month = month
            self.day = day
            self.time = time
            self.scaleEffect = scaleEffect
            self.backgroundColor = backgroundColor
            
        }
        
        
        var body: some View {
            
            VStack(spacing: 0.0) {
                
                Text(month)
                    .font(.system(size: 25, design: .monospaced)).minimumScaleFactor(0.1)
                
                Image(systemName: "hexagon")
                    .font(Font.system(size: 150, weight: Font.Weight.light))
                    .rotationEffect(Angle(degrees: 90.0))
                    .overlay(Text(day).font(.system(size: 70, design: .monospaced)).minimumScaleFactor(0.1))
                
                Text(time)
                    .font(.system(size: 25, design: .monospaced)).minimumScaleFactor(0.1)
                
            }
            .padding((backgroundColor != Color.clear) ? 16.0 : 0.0)
            .background((backgroundColor != Color.clear) ? backgroundColor.opacity(0.5).cornerRadius(20.0) : nil)
            .scaleEffect(scaleEffect)
            .shadow(radius: 10.0)
            
        }
        
    }
    

    enter image description here


    It seems using .scaleEffect() modifier does NOT modify the frame of View and it keeps and carry the original size! It may be an issue in replacing the view with other views and it cause issue about taking more space than it needs to fit! therefor I updated the codes in way that modify the frame as well to solve that issue! more on in code, the new update does not break old API, it fix the issue:

    Update:

    struct CustomDateView: View {
        
        let month: String
        let day: String
        let time: String
        let scaleEffect: CGFloat
        let backgroundColor: Color
        
        internal init(month: String, day: String, time: String, scaleEffect: CGFloat = 1.0, backgroundColor: Color = .clear) {
            
            self.month = month
            self.day = day
            self.time = time
            self.scaleEffect = ((scaleEffect > 0.0) && (scaleEffect <= 1.0)) ? scaleEffect : 1.0
            self.backgroundColor = backgroundColor
            
        }
    
        var body: some View {
            
            VStack(spacing: 0.0) {
                
                Text(month)
                    .font(Font.system(size: 25.0*scaleEffect, design: Font.Design.monospaced))
                
                Image(systemName: "hexagon")
                    .font(Font.system(size: 150.0*scaleEffect, weight: Font.Weight.light))
                    .rotationEffect(Angle(degrees: 90.0))
                    .overlay(Text(day).font(Font.system(size: 70.0*scaleEffect, design: Font.Design.monospaced)))
                
                Text(time)
                    .font(Font.system(size: 25.0*scaleEffect, design: Font.Design.monospaced))
                
            }
            .padding((backgroundColor != Color.clear) ? 16.0*scaleEffect : 0.0)
            .background((backgroundColor != Color.clear) ? backgroundColor.opacity(0.5).cornerRadius(20.0*scaleEffect) : nil)
            .shadow(radius: 10.0)
      
        }
        
    }
    

    enter image description here