Skip to content

thierryH91200/ModernLookOSXSampleSwift

Repository files navigation

Mac

A translation Modern Look OSX (Version 5.0) into Swift

Update

A Objective C version available ModernLook-OSX by gyetvan-andras

https://github.com/gyetvan-andras/ModernLook-OSX

Modern Look OSX

This project aims to provide a simple and customizable component set which can be used to create OSX applications with new style look.

Sample

A skeleton app showing ModernLook-OSX in action

IMPORTANT NOTICE

ModernLook-OSX requires OSX 10.14 to work properly. I have tested the components on OSX 10.9 with no success: generally the application runs well on 10.9, but some visual effect does not work. Probably some hacking can help, so if you can make it look better on 10.9 please let me know. However I think this new look does not fit well on older OSX UI.

Description

ModernLook-OSX is the amalgamation of different ideas found all around the net (especially on stackoverflow.com ) discussing about how to achieve the look of application rewritten by Apple for Yosemite.

So there is nothing revolutionary, however I hope somebody could find it useful.

Most of the components are derived from NS* native components and add special UI code to fit into the general ModernLook. For example, there is the MLTextField component which is derived from NSTextField. The new component initializes the NSTextField with hardcoded values (font, colors, etc.) and overrides the original painting code to add a thin line beneath the component.

MLTextField

Here is the code of the MLTextField class.

    func commonInit() {
        font = NSFont(name: "Helvetica Neue Thin", size: 16.0)
        isBordered = false
        backgroundColor = .clear
        focusRingType = .none
    }
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        NSGraphicsContext.saveGraphicsState()
        
        let bounds: NSRect = self.bounds
        textColor?.set()
        
        let bottomLine = NSBezierPath()
        var p = CGPoint.zero //bounds.origin;
        p.y = CGPoint.size.height
        bottomLine.move(to: p)
        p.x += bounds.size.width
        bottomLine.line(to: p)
        bottomLine.lineWidth = 2
        bottomLine.stroke()
        
        let heightLine = NSBezierPath()
        var p2 = CGPoint.zero //bounds.origin;
        heightLine.move(to: p2)
        p2.y = bounds.size.height
        heightLine.line(to: p2)
        heightLine.lineWidth = 2
        heightLine.stroke()

        NSGraphicsContext.restoreGraphicsState()
    }
 

You can easily change it to meet your needs, for example draw the line with foreground color, instead of hardcoded black (I plan to add this to the component soon - implemented).

Main components

ModernLook-OSX contains several components to let you create exciting applications, however not all the OSX UI covered yet. I plan to add more component to the package, so stay tuned!

MLMainWindow

This component can be used to create a window for your application.

MLWindowContent

This component is used for MLMainWindow's contentView. It draws as a colored, rounded rectangle. You can specify it's color by overriding the backgroundColor property.

MLToolbar

This component can be used for MLMainWindow's toolbar. This component has nothing common with standard NSToolbar, it is a simple NSView which just draws the window buttons. The content of the toolbar can be designed in IB. The background color can be specified with backgroundColor property, which is transparent by default.

MLContentView

You must use this component as your window content base class. The background color can be specified with backgroundColor property, which is transparent by default.

Window setup

You can design your window in IB and it is up to you how to layout stuffs. Generally you should add a toolbar and content view to the window, specifying their Custom Class properties accordingly.

Window Setup in IB

Window Setup in IB

Above you can see, that the toolbar portion of the window is a custom class of MLToolbar and the content view area is covered by an MLContentView.

You can add any other component to these views, however most of the native NS* components will look a bit strange. I have added a few components to the ModernLook-OSX and I am working on to more.

Additional components

MLTextField

Very simple subclass of NSTextField. It just sets the default font, make the component transparent and draws a thin line beneath the component.

MLComboField

It is a component similar to NSComboBox, where you can specify the content of the dropdown. It involves the following steps:

  1. add an NSTextField to your content in IB and set its custom class to MLComboField
  2. create a dropdown content provider object derived from MLComboFieldDelegate and override func createPopupContent() -> MLPopupContent?
  3. create a view controller which is derived from MLPopupContent and override func moveSelectionUp(_ up: Bool) and func moveSelection(to str: String?) -> String?
  4. add your dropdown content provider to IB and set it as delegate of the MLComboField.

MLOutlineView

This component is derived from NSOutlineView and replaces the outline view header control to add custom drawing to it.

NEW as of 03.03.2015

  • saveTreeSate
  • restoreTreeState

these calls are save/restore the state of an MLOutlineView. The state information contains the expanded and selected items and the scroll position. They work not just with static content: for example you have a tree which is loaded from a DB, let's say date based, then autosave will not work. The only expectation that the outline structure should be the same, because save/restore is row based.

NEW as of 03.04.2015

  • selectionColor

if this property is set, then it will be used as the selection background color.

MLTableView

This component is derived from NSTableView and replaces the outline view header control to add custom drawing to it.

NEW as of 03.04.2015

  • selectionColor

if this property is set, then it will be used as the selection background color.

MLRadioGroupManager

It is really not a component, just a manager to mimic Segmented Control and needs further improvements. Anyways, it works as follows: add an object with custom class as MLRadioGroupManager and an NSView to IB and place some NSButton into it. Configure the buttons style to square and type to switch. Assign the buttons' sent action to the MLRadioGroupManager. Also assign the buttons parent view to the MLRadioGroupManager as groupView. With this setup the MLRadioGroupManager will act like a radio button group, so only one button can be selected at a time. Yes, I know it is a bit confusing, but you should take a look at the sample application's toolbar.

MLAlert

It is an alert window provider. At the moment it provides just a Yes/No/Cancel possibility. You can use this component like this:

        let res = mlAlert.showQuestion("question", title: "hello", withCancel: true)
        switch res {
        case .yes:
            break
        case .no:
            break
        case .cancel:
            break
        }

MLCalendarView

Calendar component for OSX inspired by some free iOS calendar

Sample

MLCalendarView in NSPopover

Description

MLCalendarView is a date selector component which is represented as a month calendar.

The component uses the system Language & Region settings, so it will display the month and day names regarding to the system settings. Also it lays out the days according to the first day of week system setting.

Usage

MLCalendarView is derived from NSViewController and can be used as any other view.

The component contains the following properties to change the default colors used by the calendar.

    var backgroundColor = NSColor.white
    var textColor = NSColor.black
    var selectionColor = NSColor.red
    var todayMarkerColor = NSColor.green
    var dayMarkerColor = NSColor.darkGray

And the following two properties to set the selected date and the currently displayed month.

    var date =  Date()
    var selectedDate = Date()

Also, there is a delegate for the calendar, which is used to send a message when the selected date changed.

protocol MLCalendarViewDelegate: NSObjectProtocol {
    func didSelectDate(_ selectedDate: Date?)
}

MLHoverButton

It is derived from NSButton and draws itself as a filled circle with an image or text. The visuals can be specified by the following properties.

    var backgroundColor: NSColor?
    var hoveredBackgroundColor: NSColor?
    var foregroundColor: NSColor = NSColor.blue
    var _hoveredForegroundColor = NSColor.blue

MLHyperlink

It looks and behaves similar to a hyperlink in a web browser. When the mouse hover over it will change the text color and underline the text. There is a

var hoveredTextColor = NSColor.blue

property to specify the text color when hovered.

Sample Application

The provided sample application is an imagined Budget app. It has 4 separate area:

  1. Settings panel
  2. Accounts panel
  3. Budget panel
  4. Prediction panel

The application has a toolbar, where the user can switch between the application panels, by using MLRadioGroupManager. All the panels are derived from MLContentView and they use MLOutlienView, MLTableView, MLComboField and MLTextField.

NEW as of 03.10.2015

Added a calendar button to the toolbar to demonstrate MLCalendarView in action.

NEW as of 03.18.2015

Added an MLHoverButton to the toolbar to demonstrate its look.

NEW as of 03.19.2015

Change the toolbar title to be a hyperlink. When clicked it will log out a message to the console.

Known issues

  1. the window buttons on the toolbar behave strange. There are no images in them and they don't highlighted when the window is not selected, they just remain grayed out when the mouse hovered over.
  2. MLComboField dropdown remains on screen sometimes (switch to another windows, for example) - fixed.
  3. MLTextField text selection covers the bottom thin line.

License

Apache License 2.0